Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a40df37192 | ||
|
|
74721b5c12 | ||
|
|
f136549b46 | ||
|
|
4ec2b2e311 | ||
|
|
3d82ee18c7 | ||
|
|
d6eae8c091 | ||
|
|
6f8d86cd8f | ||
|
|
3b6f226389 | ||
|
|
228e4e8e1a | ||
|
|
98a42feb95 | ||
|
|
2f93ee7591 | ||
|
|
11f5cb9a05 | ||
|
|
2c00fc31bc | ||
|
|
2bbf6b34ea | ||
|
|
8d7f282743 | ||
|
|
007da014f8 | ||
|
|
b26e50f777 | ||
|
|
611bc53c9a | ||
|
|
9d36582c10 | ||
|
|
1c735a9228 | ||
|
|
1db91fcec3 | ||
|
|
8b3b947406 | ||
|
|
2c0cadf2fa | ||
|
|
5e536556da | ||
|
|
d75029f54b | ||
|
|
204c4de1f6 | ||
|
|
0f3455a4a4 | ||
|
|
72f36e7237 | ||
|
|
d988daa025 | ||
|
|
e720504855 | ||
|
|
ee51c5ad9c | ||
|
|
4ac7df7364 | ||
|
|
56f873ccb3 | ||
|
|
1f90f846f3 | ||
|
|
4a97d5a8dc | ||
|
|
0eb9b6393d | ||
|
|
56d0ea46ea | ||
|
|
a7568eebdb | ||
|
|
4049e1944a | ||
|
|
73b7fcd907 | ||
|
|
74c1a97af4 | ||
|
|
97f2a53124 | ||
|
|
b6db872be0 | ||
|
|
0d9299f900 | ||
|
|
7f928dc684 | ||
|
|
231e138ab0 | ||
|
|
361dba58c6 | ||
|
|
e1b03b4a89 | ||
|
|
0df6c737c1 | ||
|
|
12c5aeaaa4 | ||
|
|
dd3fb17dd0 | ||
|
|
627a5d4355 | ||
|
|
58629bc1df | ||
|
|
f925803b93 | ||
|
|
843adf21f7 | ||
|
|
2d1b1e0592 | ||
|
|
38e9878c04 | ||
|
|
8cb8b95c2e | ||
|
|
8e4e308ccc | ||
|
|
f0f4049798 | ||
|
|
c20fe4ac17 | ||
|
|
e9c3312612 | ||
|
|
92ad895044 | ||
|
|
bcc2d9def3 | ||
|
|
43f61359da | ||
|
|
5e37ee4566 | ||
|
|
c544e0cce8 | ||
|
|
f119a3adec | ||
|
|
c951cb87a3 | ||
|
|
08147806a0 | ||
|
|
4beb11519e | ||
|
|
b7ebda4487 | ||
|
|
b4489301ac | ||
|
|
c025e5acc6 | ||
|
|
57745f36a8 | ||
|
|
eef1c99f11 | ||
|
|
9a19919392 | ||
|
|
3e2d62fe67 | ||
|
|
6f275eb63a | ||
|
|
b12dba4d15 | ||
|
|
4ee1b04523 | ||
|
|
b1decc9853 | ||
|
|
c2e4c033ef | ||
|
|
e12223df85 | ||
|
|
468922d5ed | ||
|
|
6b9a4b480b | ||
|
|
81b482e28b | ||
|
|
c112a84c0a | ||
|
|
88ab0b5e15 | ||
|
|
717cb75720 | ||
|
|
998938490c | ||
|
|
f3beca8769 | ||
|
|
4baeb6d922 | ||
|
|
98d860aff6 | ||
|
|
11a9e3991c | ||
|
|
f380d9dc25 | ||
|
|
d546ff154f | ||
|
|
f21597d332 | ||
|
|
56d9719984 | ||
|
|
6d75dcbc32 |
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [JohnEstropia]
|
||||
2
.gitignore
vendored
@@ -12,3 +12,5 @@ Playground_macOS.playground/playground.xcworkspace/xcuserdata
|
||||
.swiftpm/xcode/package.xcworkspace/xcuserdata
|
||||
.swiftpm/xcode/xcuserdata
|
||||
Playground_iOS.playground/playground.xcworkspace/xcuserdata
|
||||
LegacyDemo/LegacyDemo.xcodeproj/xcuserdata
|
||||
Demo/Demo.xcodeproj/xcuserdata
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "7.0.0"
|
||||
s.swift_version = "5.1"
|
||||
s.version = "7.3.1"
|
||||
s.swift_version = "5.3"
|
||||
s.license = "MIT"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
s.documentation_url = "https://JohnEstropia.github.io/CoreStore"
|
||||
@@ -18,5 +18,5 @@ Pod::Spec.new do |s|
|
||||
s.public_header_files = "Sources/**/*.h"
|
||||
s.frameworks = "Foundation", "CoreData"
|
||||
s.requires_arc = true
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine' }
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine -weak_framework SwiftUI' }
|
||||
end
|
||||
|
||||
BIN
CoreStore.sketch
5
CoreStore.xcworkspace/contents.xcworkspacedata
generated
@@ -1,10 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Demo/Demo.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:CoreStore.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:CoreStoreDemo/CoreStoreDemo.xcodeproj">
|
||||
location = "group:LegacyDemo/LegacyDemo.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>B6855E48-4B19-4321-B1C7-CB2706E12777</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>CoreStoreDemo</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>github.com:JohnEstropia/CoreStore.git</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>github.com:JohnEstropia/GCDKit.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>CoreStoreDemo/CoreStoreDemo.xcodeproj</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>../../..</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>../../..Libraries/GCDKit</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:JohnEstropia/CoreStore.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>CoreStore</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>8B2E522D57154DFA93A06982C36315ECBEA4FA97</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>GCDKit</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "B6855E48-4B19-4321-B1C7-CB2706E12777",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStoreDemo",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStoreDemo\/CoreStoreDemo.xcodeproj",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/CoreStore.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/GCDKit.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>7C5E31AC-5DD0-43DA-A5C6-AF73B4532D86</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>project</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>github.com:JohnEstropia/HardcoreData.git</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>github.com:JohnEstropia/GCDKit.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.xcworkspace</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>../../..</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>../../..Libraries/GCDKit</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:JohnEstropia/HardcoreData.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>8B2E522D57154DFA93A06982C36315ECBEA4FA97</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>GCDKit</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>HardcoreData</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<<<<<<< Updated upstream
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
=======
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
|
||||
>>>>>>> Stashed changes
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<<<<<<< Updated upstream
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
=======
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439.5" width="441" height="20.5"/>
|
||||
>>>>>>> Stashed changes
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="133" width="441" height="57.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,86 +0,0 @@
|
||||
//
|
||||
// Palette.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/05.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Palette
|
||||
|
||||
final class Palette: CoreStoreObject {
|
||||
|
||||
let hue = Value.Required<Int>("hue", initial: 0)
|
||||
let saturation = Value.Required<Float>("saturation", initial: 0)
|
||||
let brightness = Value.Required<Float>("brightness", initial: 0)
|
||||
|
||||
let colorName = Value.Optional<String>(
|
||||
"colorName",
|
||||
isTransient: true,
|
||||
customGetter: Palette.getColorName
|
||||
)
|
||||
|
||||
static func randomHue() -> Int {
|
||||
|
||||
return Int(arc4random_uniform(360))
|
||||
}
|
||||
|
||||
private static func getColorName(_ partialObject: PartialObject<Palette>) -> String? {
|
||||
|
||||
if let colorName = partialObject.primitiveValue(for: { $0.colorName }) {
|
||||
|
||||
return colorName
|
||||
}
|
||||
|
||||
let colorName: String
|
||||
switch partialObject.value(for: { $0.hue }) % 360 {
|
||||
|
||||
case 0 ..< 20: colorName = "Lower Reds"
|
||||
case 20 ..< 57: colorName = "Oranges and Browns"
|
||||
case 57 ..< 90: colorName = "Yellow-Greens"
|
||||
case 90 ..< 159: colorName = "Greens"
|
||||
case 159 ..< 197: colorName = "Blue-Greens"
|
||||
case 197 ..< 241: colorName = "Blues"
|
||||
case 241 ..< 297: colorName = "Violets"
|
||||
case 297 ..< 331: colorName = "Magentas"
|
||||
default: colorName = "Upper Reds"
|
||||
}
|
||||
|
||||
partialObject.setPrimitiveValue(colorName, for: { $0.colorName })
|
||||
return colorName
|
||||
}
|
||||
}
|
||||
|
||||
extension Palette {
|
||||
|
||||
var color: UIColor {
|
||||
|
||||
return UIColor(
|
||||
hue: CGFloat(self.hue.value) / 360.0,
|
||||
saturation: CGFloat(self.saturation.value),
|
||||
brightness: CGFloat(self.brightness.value),
|
||||
alpha: 1.0
|
||||
)
|
||||
}
|
||||
|
||||
var colorText: String {
|
||||
|
||||
return "H: \(self.hue.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%"
|
||||
}
|
||||
}
|
||||
|
||||
extension Palette {
|
||||
|
||||
func setInitialValues(in transaction: BaseDataTransaction) {
|
||||
|
||||
self.hue .= Palette.randomHue()
|
||||
self.saturation .= Float(1.0)
|
||||
self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,9 @@ class BaseTestCase: XCTestCase {
|
||||
|
||||
XCTFail(error.coreStoreDumpString)
|
||||
}
|
||||
self.addTeardownBlock {
|
||||
stack.unsafeRemoveAllPersistentStoresAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
|
||||
@import CoreData;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
|
||||
// MARK: - BridgingTests
|
||||
|
||||
@implementation BridgingTests
|
||||
@@ -261,3 +265,5 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -64,7 +64,7 @@ class ConvenienceTests: BaseTestCase {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
_ = withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in
|
||||
withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in
|
||||
|
||||
let controller = transaction.createFetchedResultsController(
|
||||
From<TestEntity1>(),
|
||||
|
||||
@@ -37,78 +37,149 @@ import CoreStore
|
||||
#endif
|
||||
|
||||
class Animal: CoreStoreObject {
|
||||
|
||||
let species = Value.Required<String>("species", initial: "Swift")
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
let color = Transformable.Optional<Color>("color")
|
||||
|
||||
@Field.Stored("species")
|
||||
var species: String = "Swift"
|
||||
|
||||
@Field.Coded("color", coder: FieldCoders.NSCoding.self)
|
||||
var color: Color? = .blue
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Dog: Animal {
|
||||
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let age = Value.Required<Int>("age", initial: 1)
|
||||
let friends = Relationship.ToManyOrdered<Dog>("friends")
|
||||
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends })
|
||||
static let commonNicknames = ["Spot", "Benjie", "Max", "Milo"]
|
||||
|
||||
@Field.Stored(
|
||||
"nickname",
|
||||
dynamicInitialValue: {
|
||||
commonNicknames.randomElement()!
|
||||
}
|
||||
)
|
||||
var nickname: String
|
||||
|
||||
@Field.Stored("age")
|
||||
var age: Int = 1
|
||||
|
||||
@Field.Relationship("friends")
|
||||
var friends: [Dog]
|
||||
|
||||
@Field.Relationship("friendedBy", inverse: \.$friends)
|
||||
var friendedBy: Set<Dog>
|
||||
}
|
||||
|
||||
struct CustomType {
|
||||
var string = "customString"
|
||||
}
|
||||
|
||||
enum Job: String, CaseIterable {
|
||||
|
||||
case unemployed
|
||||
case engineer
|
||||
case doctor
|
||||
case lawyer
|
||||
|
||||
init?(data: Data) {
|
||||
|
||||
guard
|
||||
let rawValue = String(data: data, encoding: .utf8),
|
||||
let value = Self.init(rawValue: rawValue)
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self = value
|
||||
}
|
||||
|
||||
func toData() -> Data {
|
||||
|
||||
return Data(self.rawValue.utf8)
|
||||
}
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
let title = Value.Required<String>(
|
||||
|
||||
@Field.Stored(
|
||||
"title",
|
||||
initial: "Mr.",
|
||||
customSetter: Person.setTitle
|
||||
customSetter: { (object, field, newValue) in
|
||||
field.primitiveValue = newValue
|
||||
object.$displayName.primitiveValue = nil
|
||||
}
|
||||
)
|
||||
|
||||
let name = Value.Required<String>(
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored(
|
||||
"name",
|
||||
initial: "",
|
||||
customSetter: Person.setName
|
||||
customSetter: { (object, field, newValue) in
|
||||
field.primitiveValue = newValue
|
||||
object.$displayName.primitiveValue = nil
|
||||
}
|
||||
)
|
||||
|
||||
let displayName = Value.Optional<String>(
|
||||
var name: String = ""
|
||||
|
||||
@Field.Virtual(
|
||||
"displayName",
|
||||
isTransient: true,
|
||||
customGetter: Person.getDisplayName(_:),
|
||||
customGetter: Person.getDisplayName(_:_:),
|
||||
affectedByKeyPaths: Person.keyPathsAffectingDisplayName()
|
||||
)
|
||||
var displayName: String?
|
||||
|
||||
let spouse = Relationship.ToOne<Person>("spouse")
|
||||
|
||||
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master })
|
||||
@Field.Virtual(
|
||||
"customType",
|
||||
customGetter: { (object, field) in
|
||||
|
||||
private let _spouse = Relationship.ToOne<Person>("_spouseInverse", inverse: { $0.spouse })
|
||||
if let value = field.primitiveValue {
|
||||
|
||||
|
||||
private static func setTitle(_ partialObject: PartialObject<Person>, _ newValue: String) {
|
||||
|
||||
partialObject.setPrimitiveValue(newValue, for: { $0.title })
|
||||
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
|
||||
}
|
||||
|
||||
private static func setName(_ partialObject: PartialObject<Person>, _ newValue: String) {
|
||||
|
||||
partialObject.setPrimitiveValue(newValue, for: { $0.name })
|
||||
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
|
||||
}
|
||||
|
||||
static func getDisplayName(_ partialObject: PartialObject<Person>) -> String? {
|
||||
|
||||
if let displayName = partialObject.primitiveValue(for: { $0.displayName }) {
|
||||
|
||||
return displayName
|
||||
return value
|
||||
}
|
||||
let value = CustomType()
|
||||
field.primitiveValue = value
|
||||
return value
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
)
|
||||
var customField: CustomType
|
||||
|
||||
@Field.Coded(
|
||||
"job",
|
||||
coder: (
|
||||
encode: { $0.toData() },
|
||||
decode: { $0.flatMap(Job.init(data:)) ?? .unemployed }
|
||||
),
|
||||
dynamicInitialValue: {
|
||||
Job.allCases.randomElement()!
|
||||
}
|
||||
)
|
||||
var job: Job
|
||||
|
||||
@Field.Relationship("spouse")
|
||||
var spouse: Person?
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Animal>
|
||||
|
||||
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
|
||||
private var spouseInverse: Person?
|
||||
|
||||
private static func getDisplayName(_ object: ObjectProxy<Person>, _ field: ObjectProxy<Person>.FieldProxy<String?>) -> String? {
|
||||
|
||||
if let value = field.primitiveValue {
|
||||
|
||||
return value
|
||||
}
|
||||
let title = object.$title.value
|
||||
let name = object.$name.value
|
||||
let value = "\(title) \(name)"
|
||||
field.primitiveValue = value
|
||||
return value
|
||||
}
|
||||
|
||||
static func keyPathsAffectingDisplayName() -> Set<String> {
|
||||
private static func keyPathsAffectingDisplayName() -> Set<String> {
|
||||
|
||||
return [
|
||||
String(keyPath: \Person.title),
|
||||
String(keyPath: \Person.name)
|
||||
String(keyPath: \Person.$title),
|
||||
String(keyPath: \Person.$name)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -131,20 +202,20 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
],
|
||||
versionLock: [
|
||||
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
|
||||
"Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7],
|
||||
"Person": [0x2831cf046084d96d, 0xbe19b13ace54641, 0x635a082728b0f6f0, 0x3d4ef2dd4b74a87c]
|
||||
"Dog": [0xad6de93adc5565d, 0x7897e51253eba5a3, 0xd12b9ce0b13600f3, 0x5a4827cd794cd15e],
|
||||
"Person": [0xf3e6ba6016bbedc6, 0x50dedf64f0eba490, 0xa32088a0ee83468d, 0xb72d1d0b37bd0992]
|
||||
]
|
||||
)
|
||||
)
|
||||
self.prepareStack(dataStack, configurations: [nil]) { (stack) in
|
||||
|
||||
let k1 = String(keyPath: \Animal.species)
|
||||
let k1 = String(keyPath: \Animal.$species)
|
||||
XCTAssertEqual(k1, "species")
|
||||
|
||||
let k2 = String(keyPath: \Dog.species)
|
||||
let k2 = String(keyPath: \Dog.$species)
|
||||
XCTAssertEqual(k2, "species")
|
||||
|
||||
let k3 = String(keyPath: \Dog.nickname)
|
||||
let k3 = String(keyPath: \Dog.$nickname)
|
||||
XCTAssertEqual(k3, "nickname")
|
||||
|
||||
let updateDone = self.expectation(description: "update-done")
|
||||
@@ -156,27 +227,28 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let animal = transaction.create(Into<Animal>())
|
||||
XCTAssertEqual(animal.species.value, "Swift")
|
||||
XCTAssertTrue(type(of: animal.species.value) == String.self)
|
||||
XCTAssertEqual(animal.species, "Swift")
|
||||
XCTAssertTrue(type(of: animal.species) == String.self)
|
||||
XCTAssertEqual(animal.color, Color.blue)
|
||||
|
||||
animal.species .= "Sparrow"
|
||||
XCTAssertEqual(animal.species.value, "Sparrow")
|
||||
animal.species = "Sparrow"
|
||||
XCTAssertEqual(animal.species, "Sparrow")
|
||||
|
||||
animal.color .= .yellow
|
||||
XCTAssertEqual(animal.color.value, Color.yellow)
|
||||
animal.color = .yellow
|
||||
XCTAssertEqual(animal.color, Color.yellow)
|
||||
|
||||
for property in Animal.metaProperties(includeSuperclasses: true) {
|
||||
|
||||
switch property.keyPath {
|
||||
|
||||
case String(keyPath: \Animal.species):
|
||||
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>)
|
||||
case String(keyPath: \Animal.$species):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
|
||||
|
||||
case String(keyPath: \Animal.master):
|
||||
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
|
||||
case String(keyPath: \Animal.$master):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
|
||||
|
||||
case String(keyPath: \Animal.color):
|
||||
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>)
|
||||
case String(keyPath: \Animal.$color):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
|
||||
|
||||
default:
|
||||
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
|
||||
@@ -184,34 +256,34 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
let dog = transaction.create(Into<Dog>())
|
||||
XCTAssertEqual(dog.species.value, "Swift")
|
||||
XCTAssertEqual(dog.nickname.value, nil)
|
||||
XCTAssertEqual(dog.age.value, 1)
|
||||
XCTAssertEqual(dog.species, "Swift")
|
||||
XCTAssertEqual(dog.age, 1)
|
||||
XCTAssertTrue(Dog.commonNicknames.contains(dog.nickname))
|
||||
|
||||
for property in Dog.metaProperties(includeSuperclasses: true) {
|
||||
|
||||
switch property.keyPath {
|
||||
|
||||
case String(keyPath: \Dog.species):
|
||||
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>)
|
||||
case String(keyPath: \Dog.$species):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
|
||||
|
||||
case String(keyPath: \Dog.master):
|
||||
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
|
||||
case String(keyPath: \Dog.$master):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
|
||||
|
||||
case String(keyPath: \Dog.color):
|
||||
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>)
|
||||
case String(keyPath: \Dog.$color):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
|
||||
|
||||
case String(keyPath: \Dog.nickname):
|
||||
XCTAssertTrue(property is ValueContainer<Dog>.Optional<String>)
|
||||
case String(keyPath: \Dog.$nickname):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Stored<String>)
|
||||
|
||||
case String(keyPath: \Dog.age):
|
||||
XCTAssertTrue(property is ValueContainer<Dog>.Required<Int>)
|
||||
case String(keyPath: \Dog.$age):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Stored<Int>)
|
||||
|
||||
case String(keyPath: \Dog.friends):
|
||||
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyOrdered<Dog>)
|
||||
case String(keyPath: \Dog.$friends):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Relationship<[Dog]>)
|
||||
|
||||
case String(keyPath: \Dog.friendedBy):
|
||||
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyUnordered<Dog>)
|
||||
case String(keyPath: \Dog.$friendedBy):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Relationship<Set<Dog>>)
|
||||
|
||||
default:
|
||||
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
|
||||
@@ -231,17 +303,17 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
//
|
||||
// #endif
|
||||
|
||||
let didSetObserver = dog.species.observe(options: [.new, .old]) { (object, change) in
|
||||
let didSetObserver = dog.observe(\.$species, options: [.new, .old]) { (object, change) in
|
||||
|
||||
XCTAssertEqual(object, dog)
|
||||
XCTAssertEqual(change.kind, .setting)
|
||||
XCTAssertEqual(change.newValue, "Dog")
|
||||
XCTAssertEqual(change.oldValue, "Swift")
|
||||
XCTAssertFalse(change.isPrior)
|
||||
XCTAssertEqual(object.species.value, "Dog")
|
||||
XCTAssertEqual(object.species, "Dog")
|
||||
didSetObserverDone.fulfill()
|
||||
}
|
||||
let willSetObserver = dog.species.observe(options: [.new, .old, .prior]) { (object, change) in
|
||||
let willSetObserver = dog.observe(\.$species, options: [.new, .old, .prior]) { (object, change) in
|
||||
|
||||
XCTAssertEqual(object, dog)
|
||||
XCTAssertEqual(change.kind, .setting)
|
||||
@@ -250,28 +322,31 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
if change.isPrior {
|
||||
|
||||
XCTAssertNil(change.newValue)
|
||||
XCTAssertEqual(object.species.value, "Swift")
|
||||
XCTAssertEqual(object.species, "Swift")
|
||||
willSetPriorObserverDone.fulfill()
|
||||
}
|
||||
else {
|
||||
|
||||
XCTAssertEqual(change.newValue, "Dog")
|
||||
XCTAssertEqual(object.species.value, "Dog")
|
||||
XCTAssertEqual(object.species, "Dog")
|
||||
willSetNotPriorObserverDone.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
dog.species .= "Dog"
|
||||
XCTAssertEqual(dog.species.value, "Dog")
|
||||
dog.species = "Dog"
|
||||
XCTAssertEqual(dog.species, "Dog")
|
||||
|
||||
didSetObserver.invalidate()
|
||||
willSetObserver.invalidate()
|
||||
|
||||
dog.nickname .= "Spot"
|
||||
XCTAssertEqual(dog.nickname.value, "Spot")
|
||||
dog.nickname = "Spot"
|
||||
XCTAssertEqual(dog.nickname, "Spot")
|
||||
|
||||
let person = transaction.create(Into<Person>())
|
||||
XCTAssertTrue(person.pets.value.isEmpty)
|
||||
XCTAssertTrue(person.pets.isEmpty)
|
||||
XCTAssertEqual(person.customField.string, "customString")
|
||||
let initialJob = person.job
|
||||
XCTAssertTrue(Job.allCases.contains(initialJob))
|
||||
|
||||
XCTAssertEqual(
|
||||
person.rawObject!
|
||||
@@ -280,7 +355,7 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
["title", "name"]
|
||||
)
|
||||
|
||||
person.name .= "Joe"
|
||||
person.name = "Joe"
|
||||
|
||||
XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe")
|
||||
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe")
|
||||
@@ -288,46 +363,66 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
person.rawObject!.setValue("AAAA", forKey: "displayName")
|
||||
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA")
|
||||
|
||||
person.name .= "John"
|
||||
XCTAssertEqual(person.name.value, "John")
|
||||
XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter
|
||||
person.name = "John"
|
||||
XCTAssertEqual(person.name, "John")
|
||||
XCTAssertEqual(person.displayName, "Mr. John") // Custom getter
|
||||
|
||||
let personSnapshot1 = person.asSnapshot(in: transaction)!
|
||||
XCTAssertEqual(person.name.value, personSnapshot1.name)
|
||||
XCTAssertEqual(person.title.value, personSnapshot1.title)
|
||||
XCTAssertEqual(person.displayName.value, personSnapshot1.displayName)
|
||||
XCTAssertEqual(person.name, personSnapshot1.$name)
|
||||
XCTAssertEqual(person.title, personSnapshot1.$title)
|
||||
XCTAssertEqual(person.displayName, personSnapshot1.$displayName)
|
||||
XCTAssertEqual(person.job, personSnapshot1.$job)
|
||||
|
||||
person.title .= "Sir"
|
||||
XCTAssertEqual(person.displayName.value, "Sir John")
|
||||
person.title = "Sir"
|
||||
XCTAssertEqual(person.displayName, "Sir John")
|
||||
|
||||
XCTAssertEqual(personSnapshot1.name, "John")
|
||||
XCTAssertEqual(personSnapshot1.title, "Mr.")
|
||||
XCTAssertEqual(personSnapshot1.displayName, "Mr. John")
|
||||
XCTAssertEqual(personSnapshot1.$name, "John")
|
||||
XCTAssertEqual(personSnapshot1.$title, "Mr.")
|
||||
XCTAssertEqual(personSnapshot1.$displayName, "Mr. John")
|
||||
|
||||
person.customField.string = "newCustomString"
|
||||
XCTAssertEqual(person.customField.string, "newCustomString")
|
||||
|
||||
person.job = .engineer
|
||||
XCTAssertEqual(person.job, .engineer)
|
||||
|
||||
let personSnapshot2 = person.asSnapshot(in: transaction)!
|
||||
XCTAssertEqual(person.name.value, personSnapshot2.name)
|
||||
XCTAssertEqual(person.title.value, personSnapshot2.title)
|
||||
XCTAssertEqual(person.displayName.value, personSnapshot2.displayName)
|
||||
XCTAssertEqual(person.name, personSnapshot2.$name)
|
||||
XCTAssertEqual(person.title, personSnapshot2.$title)
|
||||
XCTAssertEqual(person.displayName, personSnapshot2.$displayName)
|
||||
XCTAssertEqual(person.job, personSnapshot2.$job)
|
||||
|
||||
var personSnapshot3 = personSnapshot2
|
||||
personSnapshot3.name = "James"
|
||||
XCTAssertEqual(personSnapshot1.name, "John")
|
||||
XCTAssertEqual(personSnapshot1.displayName, "Mr. John")
|
||||
XCTAssertEqual(personSnapshot2.name, "John")
|
||||
XCTAssertEqual(personSnapshot2.displayName, "Sir John")
|
||||
XCTAssertEqual(personSnapshot3.name, "James")
|
||||
XCTAssertEqual(personSnapshot3.displayName, "Sir John")
|
||||
personSnapshot3.$name = "James"
|
||||
XCTAssertEqual(personSnapshot1.$name, "John")
|
||||
XCTAssertEqual(personSnapshot1.$displayName, "Mr. John")
|
||||
XCTAssertEqual(personSnapshot1.$job, initialJob)
|
||||
XCTAssertEqual(personSnapshot2.$name, "John")
|
||||
XCTAssertEqual(personSnapshot2.$displayName, "Sir John")
|
||||
XCTAssertEqual(personSnapshot2.$job, .engineer)
|
||||
XCTAssertEqual(personSnapshot3.$name, "James")
|
||||
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
|
||||
XCTAssertEqual(personSnapshot3.$job, .engineer)
|
||||
|
||||
|
||||
|
||||
person.pets.value.insert(dog)
|
||||
person.pets.insert(dog)
|
||||
XCTAssertEqual(person.pets.count, 1)
|
||||
XCTAssertEqual(person.pets.value.first, dog)
|
||||
XCTAssertEqual(person.pets.value.first?.master.value, person)
|
||||
XCTAssertEqual(dog.master.value, person)
|
||||
XCTAssertEqual(dog.master.value?.pets.value.first, dog)
|
||||
XCTAssertEqual(person.pets.first, dog)
|
||||
XCTAssertEqual(person.pets.first?.master, person)
|
||||
XCTAssertEqual(dog.master, person)
|
||||
XCTAssertEqual(dog.master?.pets.first, dog)
|
||||
},
|
||||
success: { _ in
|
||||
|
||||
|
||||
let person = try! stack.fetchOne(From<Person>())
|
||||
XCTAssertNotNil(person)
|
||||
|
||||
let personPublisher = person!.asPublisher(in: stack)
|
||||
XCTAssertEqual(personPublisher.$name, "John")
|
||||
XCTAssertEqual(personPublisher.$displayName, "Sir John")
|
||||
XCTAssertEqual(personPublisher.$job, .engineer)
|
||||
|
||||
updateDone.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
@@ -338,59 +433,67 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let p1 = Where<Animal>({ $0.species == "Sparrow" })
|
||||
let p1 = Where<Animal>({ $0.$species == "Sparrow" })
|
||||
XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow"))
|
||||
|
||||
let bird = try transaction.fetchOne(From<Animal>(), p1)
|
||||
XCTAssertNotNil(bird)
|
||||
XCTAssertEqual(bird!.species.value, "Sparrow")
|
||||
XCTAssertEqual(bird!.species, "Sparrow")
|
||||
XCTAssertEqual(bird!.color, Color.yellow)
|
||||
|
||||
let p2 = Where<Dog>({ $0.nickname == "Spot" })
|
||||
let p2 = Where<Dog>({ $0.$nickname == "Spot" })
|
||||
XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot"))
|
||||
|
||||
let dog = try transaction.fetchOne(From<Dog>().where(\.nickname == "Spot"))
|
||||
let dog = try transaction.fetchOne(From<Dog>().where(\.$nickname == "Spot"))
|
||||
XCTAssertNotNil(dog)
|
||||
XCTAssertEqual(dog!.nickname.value, "Spot")
|
||||
XCTAssertEqual(dog!.species.value, "Dog")
|
||||
XCTAssertEqual(dog!.nickname, "Spot")
|
||||
XCTAssertEqual(dog!.species, "Dog")
|
||||
|
||||
let person = try transaction.fetchOne(From<Person>())
|
||||
XCTAssertNotNil(person)
|
||||
XCTAssertEqual(person!.pets.value.first, dog)
|
||||
XCTAssertEqual(person!.name, "John")
|
||||
XCTAssertEqual(person!.title, "Sir")
|
||||
XCTAssertEqual(person!.displayName, "Sir John")
|
||||
XCTAssertEqual(person!.customField.string, "customString")
|
||||
XCTAssertEqual(person!.job, .engineer)
|
||||
XCTAssertEqual(person!.pets.first, dog)
|
||||
|
||||
let p3 = Where<Dog>({ $0.age == 10 })
|
||||
let p3 = Where<Dog>({ $0.$age == 10 })
|
||||
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
|
||||
|
||||
let totalAge = try transaction.queryValue(From<Dog>().select(Int.self, .sum(\Dog.age)))
|
||||
let totalAge = try transaction.queryValue(
|
||||
From<Dog>().select(Int.self, .sum(\.$age))
|
||||
)
|
||||
XCTAssertEqual(totalAge, 1)
|
||||
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>()
|
||||
.where(\Animal.species == "Dog" && \Dog.age == 10)
|
||||
.where(\Animal.$species == "Dog" && \Dog.$age == 10)
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>()
|
||||
.where(\Dog.age == 10 && \Animal.species == "Dog")
|
||||
.orderBy(.ascending({ $0.species }))
|
||||
.where(\Dog.$age == 10 && \Animal.$species == "Dog")
|
||||
.orderBy(.ascending({ $0.$species }))
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.age > 10 && $0.age <= 15 })
|
||||
Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.species == "Dog" && $0.age == 10 })
|
||||
Where<Dog>({ $0.$species == "Dog" && $0.$age == 10 })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.age == 10 && $0.species == "Dog" })
|
||||
Where<Dog>({ $0.$age == 10 && $0.$species == "Dog" })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.age > 10 && $0.age <= 15 })
|
||||
Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
(\Dog.age > 10 && \Dog.age <= 15)
|
||||
(\Dog.$age > 10 && \Dog.$age <= 15)
|
||||
)
|
||||
},
|
||||
success: { _ in
|
||||
@@ -402,15 +505,20 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
self.waitForExpectations(timeout: 10, handler: { _ in })
|
||||
|
||||
self.addTeardownBlock {
|
||||
dataStack.unsafeRemoveAllPersistentStoresAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() {
|
||||
|
||||
XCTAssertEqual(String(keyPath: \Animal.species), "species")
|
||||
XCTAssertEqual(String(keyPath: \Dog.species), "species")
|
||||
XCTAssertEqual(String(keyPath: \Animal.$species), "species")
|
||||
XCTAssertEqual(String(keyPath: \Dog.$species), "species")
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
|
||||
@@ -31,6 +31,7 @@ import CoreStore
|
||||
|
||||
// MARK: - ErrorTests
|
||||
|
||||
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
|
||||
final class ErrorTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
|
||||
@@ -132,8 +132,8 @@ final class FetchTests: BaseTestDataTestCase {
|
||||
}
|
||||
)
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -267,8 +267,8 @@ final class FetchTests: BaseTestDataTestCase {
|
||||
}
|
||||
)
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
|
||||
@@ -53,7 +53,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -67,7 +67,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didInsertSectionExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -87,7 +87,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didInsertObjectExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -119,7 +119,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 2
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -184,7 +184,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -199,7 +199,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
}
|
||||
)
|
||||
|
||||
let didUpdateObjectExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -250,7 +250,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 1 || events == 2
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -329,7 +329,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -343,7 +343,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didMoveObjectExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -376,7 +376,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -437,7 +437,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -451,7 +451,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didUpdateObjectExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -480,7 +480,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 1 || events == 2
|
||||
}
|
||||
)
|
||||
let didDeleteSectionExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -508,7 +508,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
@@ -41,99 +41,26 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
// let observer = TestListObserver()
|
||||
let listPublisher = stack.listPublisher(
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// XCTAssertFalse(monitor.hasSections())
|
||||
// XCTAssertFalse(monitor.hasObjects())
|
||||
// XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
// let didInsertSectionExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 1)
|
||||
// XCTAssertEqual(
|
||||
// ((note.userInfo as NSDictionary?) ?? [:]),
|
||||
// [
|
||||
// "sectionInfo": monitor.sectionInfo(at: 0),
|
||||
// "sectionIndex": 0
|
||||
// ] as NSDictionary
|
||||
// )
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1
|
||||
// }
|
||||
// )
|
||||
// let didInsertObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 2)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["indexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
// XCTAssertEqual(object?.testNumber, NSNumber(value: 1))
|
||||
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
||||
// XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
||||
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 2
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 3
|
||||
// }
|
||||
// )
|
||||
XCTAssertFalse(listPublisher.snapshot.hasSections())
|
||||
XCTAssertFalse(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.itemIDs.isEmpty)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
@@ -161,522 +88,216 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(listPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
// @objc
|
||||
// dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
|
||||
//
|
||||
// self.prepareStack { (stack) in
|
||||
//
|
||||
// self.prepareTestDataForStack(stack)
|
||||
//
|
||||
// let observer = TestListObserver()
|
||||
// let monitor = stack.monitorSectionedList(
|
||||
// From<TestEntity1>(),
|
||||
// SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
// )
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// XCTAssertTrue(monitor.hasSections())
|
||||
// XCTAssertEqual(monitor.numberOfSections(), 2)
|
||||
// XCTAssertTrue(monitor.hasObjects())
|
||||
// XCTAssertTrue(monitor.hasObjects(in: 0))
|
||||
// XCTAssertEqual(monitor.numberOfObjects(in: 0), 2)
|
||||
// XCTAssertEqual(monitor.numberOfObjects(in: 1), 3)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// let didUpdateObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssert(events == 1 || events == 2)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["indexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
//
|
||||
// switch object?.testEntityID {
|
||||
//
|
||||
// case NSNumber(value: 101)?:
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 0), 1)
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
// XCTAssertEqual(object?.testNumber, NSNumber(value: 11))
|
||||
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
|
||||
// XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
|
||||
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
|
||||
//
|
||||
// case NSNumber(value: 102)?:
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: false))
|
||||
// XCTAssertEqual(object?.testNumber, NSNumber(value: 22))
|
||||
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
|
||||
// XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
|
||||
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
|
||||
//
|
||||
// default:
|
||||
// XCTFail()
|
||||
// }
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1 || events == 2
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 3)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 3
|
||||
// }
|
||||
// )
|
||||
// let saveExpectation = self.expectation(description: "save")
|
||||
// stack.perform(
|
||||
// asynchronous: { (transaction) -> Bool in
|
||||
//
|
||||
// if let object = try transaction.fetchOne(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
|
||||
//
|
||||
// object.testNumber = NSNumber(value: 11)
|
||||
// object.testDecimal = NSDecimalNumber(string: "11")
|
||||
// object.testString = "nil:TestEntity1:11"
|
||||
// object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
// object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// if let object = try transaction.fetchOne(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
//
|
||||
// object.testNumber = NSNumber(value: 22)
|
||||
// object.testDecimal = NSDecimalNumber(string: "22")
|
||||
// object.testString = "nil:TestEntity1:22"
|
||||
// object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
// object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// return transaction.hasChanges
|
||||
// },
|
||||
// success: { (hasChanges) in
|
||||
//
|
||||
// XCTAssertTrue(hasChanges)
|
||||
// saveExpectation.fulfill()
|
||||
// },
|
||||
// failure: { _ in
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// )
|
||||
// self.waitAndCheckExpectations()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc
|
||||
// dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
|
||||
//
|
||||
// self.prepareStack { (stack) in
|
||||
//
|
||||
// self.prepareTestDataForStack(stack)
|
||||
//
|
||||
// let observer = TestListObserver()
|
||||
// let monitor = stack.monitorSectionedList(
|
||||
// From<TestEntity1>(),
|
||||
// SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
// )
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
// let didMoveObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 1)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["fromIndexPath", "toIndexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
|
||||
// XCTAssertEqual(fromIndexPath?.index(atPosition: 0), 0)
|
||||
// XCTAssertEqual(fromIndexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
|
||||
// XCTAssertEqual(toIndexPath?.index(atPosition: 0), 1)
|
||||
// XCTAssertEqual(toIndexPath?.index(atPosition: 1), 1)
|
||||
//
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
// XCTAssertEqual(object?.testEntityID, NSNumber(value: 102))
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
//
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 2)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 2
|
||||
// }
|
||||
// )
|
||||
// let saveExpectation = self.expectation(description: "save")
|
||||
// stack.perform(
|
||||
// asynchronous: { (transaction) -> Bool in
|
||||
//
|
||||
// if let object = try transaction.fetchOne(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
//
|
||||
// object.testBoolean = NSNumber(value: true)
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// return transaction.hasChanges
|
||||
// },
|
||||
// success: { (hasChanges) in
|
||||
//
|
||||
// XCTAssertTrue(hasChanges)
|
||||
// saveExpectation.fulfill()
|
||||
// },
|
||||
// failure: { _ in
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// )
|
||||
// self.waitAndCheckExpectations()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc
|
||||
// dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
|
||||
//
|
||||
// self.prepareStack { (stack) in
|
||||
//
|
||||
// self.prepareTestDataForStack(stack)
|
||||
//
|
||||
// let observer = TestListObserver()
|
||||
// let monitor = stack.monitorSectionedList(
|
||||
// From<TestEntity1>(),
|
||||
// SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
// )
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
// let didUpdateObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssert(events == 1 || events == 2)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["indexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
//
|
||||
// XCTAssertEqual(indexPath?.section, 0)
|
||||
// XCTAssert(indexPath?.index(atPosition: 1) == 0 || indexPath?.index(atPosition: 1) == 1)
|
||||
//
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
// XCTAssertEqual(object?.isDeleted, true)
|
||||
//
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1 || events == 2
|
||||
// }
|
||||
// )
|
||||
// let didDeleteSectionExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 3)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["sectionInfo", "sectionIndex"]
|
||||
// )
|
||||
//
|
||||
// let sectionInfo = userInfo?["sectionInfo"] as? NSFetchedResultsSectionInfo
|
||||
// XCTAssertNotNil(sectionInfo)
|
||||
// XCTAssertEqual(sectionInfo?.name, "0")
|
||||
//
|
||||
// let sectionIndex = userInfo?["sectionIndex"]
|
||||
// XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(value: 0))
|
||||
//
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 3
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 4)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 4
|
||||
// }
|
||||
// )
|
||||
// let saveExpectation = self.expectation(description: "save")
|
||||
// stack.perform(
|
||||
// asynchronous: { (transaction) -> Bool in
|
||||
//
|
||||
// let count = try transaction.deleteAll(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
|
||||
// )
|
||||
// XCTAssertEqual(count, 2)
|
||||
// return transaction.hasChanges
|
||||
// },
|
||||
// success: { (hasChanges) in
|
||||
//
|
||||
// XCTAssertTrue(hasChanges)
|
||||
// saveExpectation.fulfill()
|
||||
// },
|
||||
// failure: { _ in
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// )
|
||||
// self.waitAndCheckExpectations()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//// MARK: TestListObserver
|
||||
//
|
||||
//@available(macOS 10.12, *)
|
||||
//class TestListObserver: ListSectionObserver {
|
||||
//
|
||||
// // MARK: ListObserver
|
||||
//
|
||||
// typealias ListEntityType = TestEntity1
|
||||
//
|
||||
// func listMonitorWillChange(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitorDidChange(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitorWillRefetch(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorWillRefetch:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitorDidRefetch(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorDidRefetch:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: ListObjectObserver
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "indexPath": indexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "indexPath": indexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "indexPath": indexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "fromIndexPath": fromIndexPath,
|
||||
// "toIndexPath": toIndexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: ListSectionObserver
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "sectionInfo": sectionInfo,
|
||||
// "sectionIndex": sectionIndex
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "sectionInfo": sectionInfo,
|
||||
// "sectionIndex": sectionIndex
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
@objc
|
||||
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
|
||||
|
||||
object.testNumber = NSNumber(value: 11)
|
||||
object.testDecimal = NSDecimalNumber(string: "11")
|
||||
object.testString = "nil:TestEntity1:11"
|
||||
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
|
||||
object.testNumber = NSNumber(value: 22)
|
||||
object.testDecimal = NSDecimalNumber(string: "22")
|
||||
object.testString = "nil:TestEntity1:22"
|
||||
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(listPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 4)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
|
||||
object.testBoolean = NSNumber(value: true)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(listPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 1)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 3)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
let count = try transaction.deleteAll(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
|
||||
)
|
||||
XCTAssertEqual(count, 2)
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -57,7 +57,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
var events = 0
|
||||
|
||||
let willUpdateExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -74,7 +74,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didUpdateExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -154,7 +154,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
var events = 0
|
||||
|
||||
let didDeleteExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
155
CoreStoreTests/ObjectPublisherTests.swift
Normal file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// ObjectPublisherTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - ObjectPublisherTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class ObjectPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
guard let object = try stack.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
let observer = NSObject()
|
||||
let objectPublisher = stack.publishObject(object)
|
||||
XCTAssertEqual(objectPublisher.object, object)
|
||||
XCTAssertNotNil(objectPublisher.snapshot)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
objectPublisher.addObserver(observer) { objectPublisher in
|
||||
|
||||
XCTAssertEqual(objectPublisher.object?.testNumber, NSNumber(value: 10))
|
||||
XCTAssertEqual(objectPublisher.object?.testString, "nil:TestEntity1:10")
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
XCTFail()
|
||||
try transaction.cancel()
|
||||
}
|
||||
object.testNumber = NSNumber(value: 10)
|
||||
object.testString = "nil:TestEntity1:10"
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(objectPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
guard let object = try stack.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
let observer = NSObject()
|
||||
let objectPublisher = stack.publishObject(object)
|
||||
XCTAssertEqual(objectPublisher.object, object)
|
||||
XCTAssertNotNil(objectPublisher.snapshot)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
objectPublisher.addObserver(observer) { objectPublisher in
|
||||
|
||||
XCTAssertNil(objectPublisher.object)
|
||||
XCTAssertNil(objectPublisher.snapshot)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
XCTFail()
|
||||
try transaction.cancel()
|
||||
}
|
||||
transaction.delete(object)
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(objectPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ final class TransactionTests: BaseTestCase {
|
||||
XCTAssertFalse(monitor.hasObjects())
|
||||
|
||||
var events = 0
|
||||
let willChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -414,7 +414,7 @@ final class TransactionTests: BaseTestCase {
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didInsertObjectExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -444,7 +444,7 @@ final class TransactionTests: BaseTestCase {
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
_ = self.expectation(
|
||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
@@ -622,8 +622,8 @@ final class TransactionTests: BaseTestCase {
|
||||
}
|
||||
)
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -755,8 +755,8 @@ final class TransactionTests: BaseTestCase {
|
||||
}
|
||||
)
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -896,8 +896,8 @@ final class TransactionTests: BaseTestCase {
|
||||
}
|
||||
)
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
|
||||
@@ -67,7 +67,7 @@ final class WhereTests: XCTestCase {
|
||||
dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() {
|
||||
|
||||
XCTAssertAllEqual(String(keyPath: \TestEntity1.testEntityID), "testEntityID")
|
||||
XCTAssertAllEqual(String(keyPath: \Animal.color), "color")
|
||||
XCTAssertAllEqual(String(keyPath: \Animal.$color), "color")
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -104,18 +104,18 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"master.pets",
|
||||
(\Animal.master ~ \.pets).description,
|
||||
String(keyPath: \Animal.master ~ \.pets)
|
||||
(\Animal.$master ~ \.$pets).description,
|
||||
String(keyPath: \Animal.$master ~ \.$pets)
|
||||
)
|
||||
XCTAssertAllEqual(
|
||||
"master.pets.species",
|
||||
(\Animal.master ~ \.pets ~ \.species).description,
|
||||
String(keyPath: \Animal.master ~ \.pets ~ \.species)
|
||||
(\Animal.$master ~ \.$pets ~ \.$species).description,
|
||||
String(keyPath: \Animal.$master ~ \.$pets ~ \.$species)
|
||||
)
|
||||
XCTAssertAllEqual(
|
||||
"master.pets.master",
|
||||
(\Animal.master ~ \.pets ~ \.master).description,
|
||||
String(keyPath: \Animal.master ~ \.pets ~ \.master)
|
||||
(\Animal.$master ~ \.$pets ~ \.$master).description,
|
||||
String(keyPath: \Animal.$master ~ \.$pets ~ \.$master)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"master.pets.@count",
|
||||
(\Animal.master ~ \.pets).count().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).count())
|
||||
(\Animal.$master ~ \.$pets).count().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).count())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -162,13 +162,13 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"ANY master.pets",
|
||||
(\Animal.master ~ \.pets).any().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).any())
|
||||
(\Animal.$master ~ \.$pets).any().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).any())
|
||||
)
|
||||
XCTAssertAllEqual(
|
||||
"ANY master.pets.species",
|
||||
(\Animal.master ~ \.pets ~ \.species).any().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets ~ \.species).any())
|
||||
(\Animal.$master ~ \.$pets ~ \.$species).any().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).any())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -191,13 +191,13 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"ALL master.pets",
|
||||
(\Animal.master ~ \.pets).all().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).all())
|
||||
(\Animal.$master ~ \.$pets).all().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).all())
|
||||
)
|
||||
XCTAssertAllEqual(
|
||||
"ALL master.pets.species",
|
||||
(\Animal.master ~ \.pets ~ \.species).all().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets ~ \.species).all())
|
||||
(\Animal.$master ~ \.$pets ~ \.$species).all().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).all())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -220,13 +220,13 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"NONE master.pets",
|
||||
(\Animal.master ~ \.pets).none().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).none())
|
||||
(\Animal.$master ~ \.$pets).none().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).none())
|
||||
)
|
||||
XCTAssertAllEqual(
|
||||
"NONE master.pets.species",
|
||||
(\Animal.master ~ \.pets ~ \.species).none().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets ~ \.species).none())
|
||||
(\Animal.$master ~ \.$pets ~ \.$species).none().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).none())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -247,7 +247,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.name) == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$name) == dummy
|
||||
let predicate = NSPredicate(format: "master.name == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -265,7 +265,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.spouse ~ \.name) == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$spouse ~ \.$name) == dummy
|
||||
let predicate = NSPredicate(format: "master.spouse.name == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -283,7 +283,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets).count() == count
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets).count() == count
|
||||
let predicate = NSPredicate(format: "master.pets.@count == %d", count)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -301,7 +301,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).any() == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).any() == dummy
|
||||
let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -319,7 +319,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).all() == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).all() == dummy
|
||||
let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -337,7 +337,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).none() == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).none() == dummy
|
||||
let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
|
||||
973
Demo/Demo.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,973 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */; };
|
||||
B531EFE924EB5A53005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */; };
|
||||
B531EFEB24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */; };
|
||||
B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */; };
|
||||
B54D2F7A25119540004BEC7D /* Advanced.EvolutionDemo.V2.FromV1.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F7925119540004BEC7D /* Advanced.EvolutionDemo.V2.FromV1.xcmappingmodel */; };
|
||||
B54D2F7C251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F7B251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift */; };
|
||||
B54D2F7E25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F7D25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel */; };
|
||||
B54D2F852511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F842511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift */; };
|
||||
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */; };
|
||||
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */; };
|
||||
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */; };
|
||||
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */; };
|
||||
B5931B4225124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4125124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift */; };
|
||||
B5931B442512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B432512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift */; };
|
||||
B5931B46251248B0007DA58E /* Advanced.EvolutionDemo.V3.FromV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B45251248B0007DA58E /* Advanced.EvolutionDemo.V3.FromV2.swift */; };
|
||||
B5931B4825124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4725124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift */; };
|
||||
B5931B4A25124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */; };
|
||||
B5931B4C251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */; };
|
||||
B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911C24E5429200E7E8BD /* AppDelegate.swift */; };
|
||||
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */; };
|
||||
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3912024E5429200E7E8BD /* Menu.MainView.swift */; };
|
||||
B5A3912324E5429300E7E8BD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912224E5429300E7E8BD /* Images.xcassets */; };
|
||||
B5A3912624E5429300E7E8BD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */; };
|
||||
B5A3912924E5429300E7E8BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912724E5429300E7E8BD /* LaunchScreen.storyboard */; };
|
||||
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3913324E6170500E7E8BD /* Menu.swift */; };
|
||||
B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915224E6537F00E7E8BD /* Menu.ItemView.swift */; };
|
||||
B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915824E685EC00E7E8BD /* Classic.swift */; };
|
||||
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915A24E685FE00E7E8BD /* Modern.swift */; };
|
||||
B5A3915E24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915D24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift */; };
|
||||
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */; };
|
||||
B5A3916224E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */; };
|
||||
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */; };
|
||||
B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; };
|
||||
B5A3916C24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5A3916D24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916824E698F900E7E8BD /* CoreStore.framework */; };
|
||||
B5A3916E24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916824E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5A3916F24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916924E698F900E7E8BD /* CoreStore.framework */; };
|
||||
B5A3917024E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916924E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5A3917124E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916A24E698F900E7E8BD /* CoreStore.framework */; };
|
||||
B5A3917224E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916A24E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917424E6990200E7E8BD /* MapKit.framework */; };
|
||||
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917624E6990700E7E8BD /* CoreLocation.framework */; };
|
||||
B5A3917924E6991600E7E8BD /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917824E6991600E7E8BD /* SwiftUI.framework */; };
|
||||
B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917B24E6A76C00E7E8BD /* LazyView.swift */; };
|
||||
B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */; };
|
||||
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917F24E787D900E7E8BD /* InstructionsView.swift */; };
|
||||
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */; };
|
||||
B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918524E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift */; };
|
||||
B5A3918824E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */; };
|
||||
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */; };
|
||||
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */; };
|
||||
B5A3918F24E7E06500E7E8BD /* Modern.ColorsDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */; };
|
||||
B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919124E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift */; };
|
||||
B5A3919424E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */; };
|
||||
B5A3919624E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */; };
|
||||
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */; };
|
||||
B5A3919A24E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */; };
|
||||
B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */; };
|
||||
B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */; };
|
||||
B5A391A224E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A124E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift */; };
|
||||
B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */; };
|
||||
B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */; };
|
||||
B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */; };
|
||||
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */; };
|
||||
B5A391AE24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391AD24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift */; };
|
||||
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */; };
|
||||
B5A391B424E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */; };
|
||||
B5A391B924E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */; };
|
||||
B5A391BB24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */; };
|
||||
B5A543D724FB7478000DC5E3 /* Classic.ColorsDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543D624FB7478000DC5E3 /* Classic.ColorsDemo.swift */; };
|
||||
B5A543DB24FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5A543D924FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld */; };
|
||||
B5A543DD24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543DC24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift */; };
|
||||
B5A543E724FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543E624FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift */; };
|
||||
B5A543E924FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543E824FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift */; };
|
||||
B5A543EB24FB84AF000DC5E3 /* ⭐️Classic.ColorsDemo.DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543EA24FB84AF000DC5E3 /* ⭐️Classic.ColorsDemo.DetailViewController.swift */; };
|
||||
B5A543ED24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543EC24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift */; };
|
||||
B5A543EF24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543EE24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift */; };
|
||||
B5A543F124FB84DD000DC5E3 /* ⭐️Classic.ColorsDemo.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543F024FB84DD000DC5E3 /* ⭐️Classic.ColorsDemo.ListViewController.swift */; };
|
||||
B5A543F324FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543F224FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift */; };
|
||||
B5A543F624FBF13A000DC5E3 /* Advanced.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543F524FBF13A000DC5E3 /* Advanced.swift */; };
|
||||
B5A543FB2504840E000DC5E3 /* Advanced.EvolutionDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543FA2504840E000DC5E3 /* Advanced.EvolutionDemo.swift */; };
|
||||
B5A543FF250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543FE250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift */; };
|
||||
B5A54401250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A54400250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift */; };
|
||||
B5A54403250487D5000DC5E3 /* Advanced.EvolutionDemo.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A54402250487D4000DC5E3 /* Advanced.EvolutionDemo.ItemView.swift */; };
|
||||
B5A54405250487F5000DC5E3 /* Advanced.EvolutionDemo.CreatureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A54404250487F5000DC5E3 /* Advanced.EvolutionDemo.CreatureType.swift */; };
|
||||
B5A5440725049480000DC5E3 /* Advanced.EvolutionDemo.V1.Creature.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5440625049480000DC5E3 /* Advanced.EvolutionDemo.V1.Creature.swift */; };
|
||||
B5A5440925049489000DC5E3 /* Advanced.EvolutionDemo.V2.Creature.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5440825049489000DC5E3 /* Advanced.EvolutionDemo.V2.Creature.swift */; };
|
||||
B5A5440B25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5440A25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift */; };
|
||||
B5A5440D2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5440C2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift */; };
|
||||
B5C18F3325138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C18F3225138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift */; };
|
||||
B5D6F1F8250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F1F7250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift */; };
|
||||
B5D6F1FC250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F1FB250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift */; };
|
||||
B5D6F1FE250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F1FD250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift */; };
|
||||
B5D6F205250E0DD600DF5D2F /* Advanced.EvolutionDemo.V3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F204250E0DD600DF5D2F /* Advanced.EvolutionDemo.V3.swift */; };
|
||||
B5D6F207250E0E3B00DF5D2F /* Advanced.EvolutionDemo.V4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F206250E0E3B00DF5D2F /* Advanced.EvolutionDemo.V4.swift */; };
|
||||
B5D6F209250E14AA00DF5D2F /* ⭐️Advanced.EvolutionDemo.Migrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F208250E14AA00DF5D2F /* ⭐️Advanced.EvolutionDemo.Migrator.swift */; };
|
||||
B5D6F210250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F20E250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld */; };
|
||||
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
B5A3917324E698F900E7E8BD /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B5A3916C24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */,
|
||||
B5A3917224E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */,
|
||||
B5A3917024E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */,
|
||||
B5A3916E24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.PlaceholderView.swift; sourceTree = "<group>"; };
|
||||
B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.PokedexEntry.swift"; sourceTree = "<group>"; };
|
||||
B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.Service.swift"; sourceTree = "<group>"; };
|
||||
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.MainView.swift; sourceTree = "<group>"; };
|
||||
B54D2F7925119540004BEC7D /* Advanced.EvolutionDemo.V2.FromV1.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Advanced.EvolutionDemo.V2.FromV1.xcmappingmodel; sourceTree = "<group>"; };
|
||||
B54D2F7B251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
B54D2F7D25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel; sourceTree = "<group>"; };
|
||||
B54D2F842511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.CreaturesDataSource.swift; sourceTree = "<group>"; };
|
||||
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ListView.swift; sourceTree = "<group>"; };
|
||||
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ListViewController.swift; sourceTree = "<group>"; };
|
||||
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ItemCell.swift; sourceTree = "<group>"; };
|
||||
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Details.swift; sourceTree = "<group>"; };
|
||||
B5931B4125124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V1.FromV2.swift; sourceTree = "<group>"; };
|
||||
B5931B432512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV1.swift; sourceTree = "<group>"; };
|
||||
B5931B45251248B0007DA58E /* Advanced.EvolutionDemo.V3.FromV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.FromV2.swift; sourceTree = "<group>"; };
|
||||
B5931B4725124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V4.FromV3.swift; sourceTree = "<group>"; };
|
||||
B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.FromV4.swift; sourceTree = "<group>"; };
|
||||
B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV3.swift; sourceTree = "<group>"; };
|
||||
B5A3911924E5429200E7E8BD /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5A3911C24E5429200E7E8BD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
B5A3912024E5429200E7E8BD /* Menu.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.MainView.swift; sourceTree = "<group>"; };
|
||||
B5A3912224E5429300E7E8BD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
B5A3912824E5429300E7E8BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
B5A3913324E6170500E7E8BD /* Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; };
|
||||
B5A3913924E62A9A00E7E8BD /* Rakefile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = Rakefile; sourceTree = SOURCE_ROOT; };
|
||||
B5A3914124E62D3900E7E8BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B5A3915224E6537F00E7E8BD /* Menu.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.ItemView.swift; sourceTree = "<group>"; };
|
||||
B5A3915824E685EC00E7E8BD /* Classic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.swift; sourceTree = "<group>"; };
|
||||
B5A3915A24E685FE00E7E8BD /* Modern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.swift; sourceTree = "<group>"; };
|
||||
B5A3915D24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PlacemarksDemo.swift"; sourceTree = "<group>"; };
|
||||
B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.MapView.swift; sourceTree = "<group>"; };
|
||||
B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PlacemarksDemo.MainView.swift"; sourceTree = "<group>"; };
|
||||
B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.Place.swift; sourceTree = "<group>"; };
|
||||
B5A3916724E698F900E7E8BD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5A3916824E698F900E7E8BD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5A3916924E698F900E7E8BD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5A3916A24E698F900E7E8BD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5A3917424E6990200E7E8BD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B5A3917624E6990700E7E8BD /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B5A3917824E6991600E7E8BD /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B5A3917B24E6A76C00E7E8BD /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
||||
B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.Geocoder.swift; sourceTree = "<group>"; };
|
||||
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsView.swift; sourceTree = "<group>"; };
|
||||
B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.swift; sourceTree = "<group>"; };
|
||||
B5A3918524E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.TimeZone.swift; sourceTree = "<group>"; };
|
||||
B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.TimeZonesDemo.MainView.swift"; sourceTree = "<group>"; };
|
||||
B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.ListView.swift; sourceTree = "<group>"; };
|
||||
B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.ItemView.swift; sourceTree = "<group>"; };
|
||||
B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.swift; sourceTree = "<group>"; };
|
||||
B5A3919124E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.Palette.swift; sourceTree = "<group>"; };
|
||||
B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.SwiftUI.ListView.swift"; sourceTree = "<group>"; };
|
||||
B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift"; sourceTree = "<group>"; };
|
||||
B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.Filter.swift; sourceTree = "<group>"; };
|
||||
B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift"; sourceTree = "<group>"; };
|
||||
B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.SwiftUI.swift; sourceTree = "<group>"; };
|
||||
B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.swift; sourceTree = "<group>"; };
|
||||
B5A391A124E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.UIKit.ListViewController.swift"; sourceTree = "<group>"; };
|
||||
B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ListView.swift; sourceTree = "<group>"; };
|
||||
B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.MainView.swift"; sourceTree = "<group>"; };
|
||||
B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ItemCell.swift; sourceTree = "<group>"; };
|
||||
B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.DetailView.swift; sourceTree = "<group>"; };
|
||||
B5A391AD24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift"; sourceTree = "<group>"; };
|
||||
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.swift; sourceTree = "<group>"; };
|
||||
B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.Form.swift"; sourceTree = "<group>"; };
|
||||
B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.Species.swift"; sourceTree = "<group>"; };
|
||||
B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.PokemonType.swift"; sourceTree = "<group>"; };
|
||||
B5A543D624FB7478000DC5E3 /* Classic.ColorsDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.swift; sourceTree = "<group>"; };
|
||||
B5A543DA24FB7513000DC5E3 /* ColorsDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ColorsDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5A543DC24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.Palette.swift; sourceTree = "<group>"; };
|
||||
B5A543E624FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.Filter.swift; sourceTree = "<group>"; };
|
||||
B5A543E824FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.DetailView.swift; sourceTree = "<group>"; };
|
||||
B5A543EA24FB84AF000DC5E3 /* ⭐️Classic.ColorsDemo.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Classic.ColorsDemo.DetailViewController.swift"; sourceTree = "<group>"; };
|
||||
B5A543EC24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.MainView.swift; sourceTree = "<group>"; };
|
||||
B5A543EE24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.LIstView.swift; sourceTree = "<group>"; };
|
||||
B5A543F024FB84DD000DC5E3 /* ⭐️Classic.ColorsDemo.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Classic.ColorsDemo.ListViewController.swift"; sourceTree = "<group>"; };
|
||||
B5A543F224FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.ItemCell.swift; sourceTree = "<group>"; };
|
||||
B5A543F524FBF13A000DC5E3 /* Advanced.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.swift; sourceTree = "<group>"; };
|
||||
B5A543FA2504840E000DC5E3 /* Advanced.EvolutionDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.swift; sourceTree = "<group>"; };
|
||||
B5A543FE250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.MainView.swift; sourceTree = "<group>"; };
|
||||
B5A54400250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.ListView.swift; sourceTree = "<group>"; };
|
||||
B5A54402250487D4000DC5E3 /* Advanced.EvolutionDemo.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.ItemView.swift; sourceTree = "<group>"; };
|
||||
B5A54404250487F5000DC5E3 /* Advanced.EvolutionDemo.CreatureType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.CreatureType.swift; sourceTree = "<group>"; };
|
||||
B5A5440625049480000DC5E3 /* Advanced.EvolutionDemo.V1.Creature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V1.Creature.swift; sourceTree = "<group>"; };
|
||||
B5A5440825049489000DC5E3 /* Advanced.EvolutionDemo.V2.Creature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.Creature.swift; sourceTree = "<group>"; };
|
||||
B5A5440A25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.Creature.swift; sourceTree = "<group>"; };
|
||||
B5A5440C2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V4.Creature.swift; sourceTree = "<group>"; };
|
||||
B5C18F3225138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.ProgressView.swift; sourceTree = "<group>"; };
|
||||
B5D6F1F7250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V1.swift; sourceTree = "<group>"; };
|
||||
B5D6F1FB250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.swift; sourceTree = "<group>"; };
|
||||
B5D6F1FD250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.GeologicalPeriod.swift; sourceTree = "<group>"; };
|
||||
B5D6F204250E0DD600DF5D2F /* Advanced.EvolutionDemo.V3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.swift; sourceTree = "<group>"; };
|
||||
B5D6F206250E0E3B00DF5D2F /* Advanced.EvolutionDemo.V4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V4.swift; sourceTree = "<group>"; };
|
||||
B5D6F208250E14AA00DF5D2F /* ⭐️Advanced.EvolutionDemo.Migrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Advanced.EvolutionDemo.Migrator.swift"; sourceTree = "<group>"; };
|
||||
B5D6F20F250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Advanced.EvolutionDemo.V1.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5D6F211250E1E7000DF5D2F /* Advanced.EvolutionDemo.V2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Advanced.EvolutionDemo.V2.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
B5A3911624E5429200E7E8BD /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */,
|
||||
B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */,
|
||||
B5A3917124E698F900E7E8BD /* CoreStore.framework in Frameworks */,
|
||||
B5A3917924E6991600E7E8BD /* SwiftUI.framework in Frameworks */,
|
||||
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */,
|
||||
B5A3916F24E698F900E7E8BD /* CoreStore.framework in Frameworks */,
|
||||
B5A3916D24E698F900E7E8BD /* CoreStore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
B5A3910E24E5424E00E7E8BD = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3913D24E62C6C00E7E8BD /* Metadata */,
|
||||
B5A3911B24E5429200E7E8BD /* ⭐️Sources */,
|
||||
B5A3913E24E62CB200E7E8BD /* Resources */,
|
||||
B5A3911A24E5429200E7E8BD /* Products */,
|
||||
B5A3916624E698F900E7E8BD /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3911A24E5429200E7E8BD /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3911924E5429200E7E8BD /* Demo.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3911B24E5429200E7E8BD /* ⭐️Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3911C24E5429200E7E8BD /* AppDelegate.swift */,
|
||||
B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */,
|
||||
B5A3915524E6858A00E7E8BD /* ⭐️Demos */,
|
||||
B5A3917A24E6A75F00E7E8BD /* Helpers */,
|
||||
);
|
||||
path = "⭐️Sources";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3912424E5429300E7E8BD /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3913D24E62C6C00E7E8BD /* Metadata */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3913924E62A9A00E7E8BD /* Rakefile */,
|
||||
B5A3914124E62D3900E7E8BD /* Info.plist */,
|
||||
);
|
||||
name = Metadata;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3913E24E62CB200E7E8BD /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3912724E5429300E7E8BD /* LaunchScreen.storyboard */,
|
||||
B5A3912224E5429300E7E8BD /* Images.xcassets */,
|
||||
B5A3912424E5429300E7E8BD /* Preview Content */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3915424E6857F00E7E8BD /* Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3913324E6170500E7E8BD /* Menu.swift */,
|
||||
B5A3912024E5429200E7E8BD /* Menu.MainView.swift */,
|
||||
B5A3915224E6537F00E7E8BD /* Menu.ItemView.swift */,
|
||||
B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */,
|
||||
);
|
||||
path = Menu;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3915524E6858A00E7E8BD /* ⭐️Demos */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3915624E685B700E7E8BD /* ⭐️Modern */,
|
||||
B5A3915724E685D300E7E8BD /* ⭐️Classic */,
|
||||
B5A543F424FBF107000DC5E3 /* ⭐️Advanced */,
|
||||
);
|
||||
path = "⭐️Demos";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3915624E685B700E7E8BD /* ⭐️Modern */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3915A24E685FE00E7E8BD /* Modern.swift */,
|
||||
B5A3915C24E6921E00E7E8BD /* ⭐️PlacemarksDemo */,
|
||||
B5A3918124E7A1EF00E7E8BD /* ⭐️TimeZonesDemo */,
|
||||
B5A3918D24E7DE7A00E7E8BD /* ⭐️ColorsDemo */,
|
||||
B5A391AF24E96AD600E7E8BD /* ⭐️PokedexDemo */,
|
||||
);
|
||||
path = "⭐️Modern";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3915724E685D300E7E8BD /* ⭐️Classic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3915824E685EC00E7E8BD /* Classic.swift */,
|
||||
B5A543D524FB73B2000DC5E3 /* ⭐️ColorsDemo */,
|
||||
);
|
||||
path = "⭐️Classic";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3915C24E6921E00E7E8BD /* ⭐️PlacemarksDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3915D24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift */,
|
||||
B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */,
|
||||
B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */,
|
||||
B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */,
|
||||
B5A3916324E698B300E7E8BD /* Models */,
|
||||
);
|
||||
path = "⭐️PlacemarksDemo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3916324E698B300E7E8BD /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */,
|
||||
);
|
||||
name = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3916624E698F900E7E8BD /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3917824E6991600E7E8BD /* SwiftUI.framework */,
|
||||
B5A3917624E6990700E7E8BD /* CoreLocation.framework */,
|
||||
B5A3917424E6990200E7E8BD /* MapKit.framework */,
|
||||
B5A3916724E698F900E7E8BD /* CoreStore.framework */,
|
||||
B5A3916824E698F900E7E8BD /* CoreStore.framework */,
|
||||
B5A3916924E698F900E7E8BD /* CoreStore.framework */,
|
||||
B5A3916A24E698F900E7E8BD /* CoreStore.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3917A24E6A75F00E7E8BD /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3917B24E6A76C00E7E8BD /* LazyView.swift */,
|
||||
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
|
||||
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */,
|
||||
B5A3915424E6857F00E7E8BD /* Menu */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3918124E7A1EF00E7E8BD /* ⭐️TimeZonesDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */,
|
||||
B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */,
|
||||
B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */,
|
||||
B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */,
|
||||
B5A3918424E7A53300E7E8BD /* Models */,
|
||||
);
|
||||
path = "⭐️TimeZonesDemo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3918424E7A53300E7E8BD /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3918524E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift */,
|
||||
);
|
||||
name = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3918D24E7DE7A00E7E8BD /* ⭐️ColorsDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */,
|
||||
B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */,
|
||||
B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */,
|
||||
B5A3919C24E8EE9000E7E8BD /* ⭐️UIKit */,
|
||||
B5A3919B24E8EE8100E7E8BD /* ⭐️SwiftUI */,
|
||||
B5A3919024E7E0B000E7E8BD /* Models */,
|
||||
);
|
||||
path = "⭐️ColorsDemo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3919024E7E0B000E7E8BD /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3919124E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift */,
|
||||
);
|
||||
name = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3919B24E8EE8100E7E8BD /* ⭐️SwiftUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */,
|
||||
B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */,
|
||||
B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */,
|
||||
B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */,
|
||||
);
|
||||
name = "⭐️SwiftUI";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A3919C24E8EE9000E7E8BD /* ⭐️UIKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */,
|
||||
B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */,
|
||||
B5A391A124E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift */,
|
||||
B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */,
|
||||
B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */,
|
||||
B5A391AD24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift */,
|
||||
);
|
||||
name = "⭐️UIKit";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A391AF24E96AD600E7E8BD /* ⭐️PokedexDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
|
||||
B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */,
|
||||
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */,
|
||||
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */,
|
||||
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */,
|
||||
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */,
|
||||
B5A391B224E96B7400E7E8BD /* ⭐️Models */,
|
||||
);
|
||||
path = "⭐️PokedexDemo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A391B224E96B7400E7E8BD /* ⭐️Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */,
|
||||
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */,
|
||||
B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */,
|
||||
B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */,
|
||||
B5A391B724E96E8600E7E8BD /* ⭐️Attributes */,
|
||||
);
|
||||
name = "⭐️Models";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A391B724E96E8600E7E8BD /* ⭐️Attributes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */,
|
||||
);
|
||||
name = "⭐️Attributes";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A543D524FB73B2000DC5E3 /* ⭐️ColorsDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A543D624FB7478000DC5E3 /* Classic.ColorsDemo.swift */,
|
||||
B5A543E624FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift */,
|
||||
B5A543EC24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift */,
|
||||
B5A543EE24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift */,
|
||||
B5A543F024FB84DD000DC5E3 /* ⭐️Classic.ColorsDemo.ListViewController.swift */,
|
||||
B5A543F224FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift */,
|
||||
B5A543E824FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift */,
|
||||
B5A543EA24FB84AF000DC5E3 /* ⭐️Classic.ColorsDemo.DetailViewController.swift */,
|
||||
B5A543D824FB7483000DC5E3 /* Models */,
|
||||
);
|
||||
path = "⭐️ColorsDemo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A543D824FB7483000DC5E3 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A543D924FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld */,
|
||||
B5A543DC24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift */,
|
||||
);
|
||||
name = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A543F424FBF107000DC5E3 /* ⭐️Advanced */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A543F524FBF13A000DC5E3 /* Advanced.swift */,
|
||||
B5A543F8250482EF000DC5E3 /* ⭐️EvolutionDemo */,
|
||||
);
|
||||
path = "⭐️Advanced";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A543F8250482EF000DC5E3 /* ⭐️EvolutionDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A543FA2504840E000DC5E3 /* Advanced.EvolutionDemo.swift */,
|
||||
B5D6F1FD250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift */,
|
||||
B5A543FE250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift */,
|
||||
B5A54400250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift */,
|
||||
B5A54402250487D4000DC5E3 /* Advanced.EvolutionDemo.ItemView.swift */,
|
||||
B5D6F208250E14AA00DF5D2F /* ⭐️Advanced.EvolutionDemo.Migrator.swift */,
|
||||
B5C18F3225138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift */,
|
||||
B54D2F842511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift */,
|
||||
B5A543FD25048794000DC5E3 /* Models */,
|
||||
);
|
||||
path = "⭐️EvolutionDemo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A543FD25048794000DC5E3 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A54404250487F5000DC5E3 /* Advanced.EvolutionDemo.CreatureType.swift */,
|
||||
B5D6F20E250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld */,
|
||||
B5D6F1F9250E08D200DF5D2F /* V1 */,
|
||||
B5D6F1FA250E0AE000DF5D2F /* V2 */,
|
||||
B5D6F202250E0D9C00DF5D2F /* V3 */,
|
||||
B5D6F203250E0DA400DF5D2F /* V4 */,
|
||||
);
|
||||
name = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5D6F1F9250E08D200DF5D2F /* V1 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D6F1F7250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift */,
|
||||
B5A5440625049480000DC5E3 /* Advanced.EvolutionDemo.V1.Creature.swift */,
|
||||
B5931B4125124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift */,
|
||||
B54D2F7D25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel */,
|
||||
);
|
||||
name = V1;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5D6F1FA250E0AE000DF5D2F /* V2 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D6F1FB250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift */,
|
||||
B5A5440825049489000DC5E3 /* Advanced.EvolutionDemo.V2.Creature.swift */,
|
||||
B5931B432512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift */,
|
||||
B54D2F7925119540004BEC7D /* Advanced.EvolutionDemo.V2.FromV1.xcmappingmodel */,
|
||||
B54D2F7B251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift */,
|
||||
B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */,
|
||||
);
|
||||
name = V2;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5D6F202250E0D9C00DF5D2F /* V3 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D6F204250E0DD600DF5D2F /* Advanced.EvolutionDemo.V3.swift */,
|
||||
B5A5440A25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift */,
|
||||
B5931B45251248B0007DA58E /* Advanced.EvolutionDemo.V3.FromV2.swift */,
|
||||
B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */,
|
||||
);
|
||||
name = V3;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5D6F203250E0DA400DF5D2F /* V4 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D6F206250E0E3B00DF5D2F /* Advanced.EvolutionDemo.V4.swift */,
|
||||
B5A5440C2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift */,
|
||||
B5931B4725124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift */,
|
||||
);
|
||||
name = V4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
B5A3911824E5429200E7E8BD /* Demo */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B5A3912B24E5429300E7E8BD /* Build configuration list for PBXNativeTarget "Demo" */;
|
||||
buildPhases = (
|
||||
B5A3911524E5429200E7E8BD /* Sources */,
|
||||
B5A3911624E5429200E7E8BD /* Frameworks */,
|
||||
B5A3911724E5429200E7E8BD /* Resources */,
|
||||
B5A3917324E698F900E7E8BD /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Demo;
|
||||
productName = Demo;
|
||||
productReference = B5A3911924E5429200E7E8BD /* Demo.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
B5A3910F24E5424E00E7E8BD /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1160;
|
||||
LastUpgradeCheck = 1160;
|
||||
TargetAttributes = {
|
||||
B5A3911824E5429200E7E8BD = {
|
||||
CreatedOnToolsVersion = 11.6;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = B5A3911224E5424E00E7E8BD /* Build configuration list for PBXProject "Demo" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = B5A3910E24E5424E00E7E8BD;
|
||||
productRefGroup = B5A3911A24E5429200E7E8BD /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
B5A3911824E5429200E7E8BD /* Demo */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
B5A3911724E5429200E7E8BD /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5A3912924E5429300E7E8BD /* LaunchScreen.storyboard in Resources */,
|
||||
B5A3912624E5429300E7E8BD /* Preview Assets.xcassets in Resources */,
|
||||
B5A3912324E5429300E7E8BD /* Images.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
B5A3911524E5429200E7E8BD /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5A543F624FBF13A000DC5E3 /* Advanced.swift in Sources */,
|
||||
B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */,
|
||||
B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */,
|
||||
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */,
|
||||
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */,
|
||||
B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */,
|
||||
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */,
|
||||
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */,
|
||||
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */,
|
||||
B5A543FB2504840E000DC5E3 /* Advanced.EvolutionDemo.swift in Sources */,
|
||||
B5A543D724FB7478000DC5E3 /* Classic.ColorsDemo.swift in Sources */,
|
||||
B5A543DB24FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld in Sources */,
|
||||
B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */,
|
||||
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */,
|
||||
B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */,
|
||||
B5A3918F24E7E06500E7E8BD /* Modern.ColorsDemo.swift in Sources */,
|
||||
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */,
|
||||
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */,
|
||||
B5A3915E24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift in Sources */,
|
||||
B5A54405250487F5000DC5E3 /* Advanced.EvolutionDemo.CreatureType.swift in Sources */,
|
||||
B54D2F852511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift in Sources */,
|
||||
B5D6F1FE250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift in Sources */,
|
||||
B5A54403250487D5000DC5E3 /* Advanced.EvolutionDemo.ItemView.swift in Sources */,
|
||||
B5A54401250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift in Sources */,
|
||||
B5A543FF250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift in Sources */,
|
||||
B5D6F209250E14AA00DF5D2F /* ⭐️Advanced.EvolutionDemo.Migrator.swift in Sources */,
|
||||
B5C18F3325138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift in Sources */,
|
||||
B5D6F1F8250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift in Sources */,
|
||||
B5D6F210250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld in Sources */,
|
||||
B5D6F1FC250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift in Sources */,
|
||||
B5D6F205250E0DD600DF5D2F /* Advanced.EvolutionDemo.V3.swift in Sources */,
|
||||
B5D6F207250E0E3B00DF5D2F /* Advanced.EvolutionDemo.V4.swift in Sources */,
|
||||
B5A543E924FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift in Sources */,
|
||||
B5A543E724FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift in Sources */,
|
||||
B5A543F324FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift in Sources */,
|
||||
B5A543EF24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift in Sources */,
|
||||
B5A543ED24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift in Sources */,
|
||||
B5A543DD24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift in Sources */,
|
||||
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */,
|
||||
B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */,
|
||||
B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */,
|
||||
B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */,
|
||||
B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */,
|
||||
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */,
|
||||
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */,
|
||||
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */,
|
||||
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */,
|
||||
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */,
|
||||
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */,
|
||||
B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */,
|
||||
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */,
|
||||
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */,
|
||||
B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */,
|
||||
B5A543EB24FB84AF000DC5E3 /* ⭐️Classic.ColorsDemo.DetailViewController.swift in Sources */,
|
||||
B5A543F124FB84DD000DC5E3 /* ⭐️Classic.ColorsDemo.ListViewController.swift in Sources */,
|
||||
B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */,
|
||||
B5A3916224E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift in Sources */,
|
||||
B5A391B424E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift in Sources */,
|
||||
B531EFE924EB5A53005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift in Sources */,
|
||||
B5A391BB24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift in Sources */,
|
||||
B531EFEB24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift in Sources */,
|
||||
B5A391B924E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift in Sources */,
|
||||
B5A3918824E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift in Sources */,
|
||||
B5A5440725049480000DC5E3 /* Advanced.EvolutionDemo.V1.Creature.swift in Sources */,
|
||||
B5931B4225124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift in Sources */,
|
||||
B54D2F7E25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel in Sources */,
|
||||
B5A5440925049489000DC5E3 /* Advanced.EvolutionDemo.V2.Creature.swift in Sources */,
|
||||
B5931B442512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift in Sources */,
|
||||
B54D2F7A25119540004BEC7D /* Advanced.EvolutionDemo.V2.FromV1.xcmappingmodel in Sources */,
|
||||
B54D2F7C251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift in Sources */,
|
||||
B5931B4C251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift in Sources */,
|
||||
B5A5440B25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift in Sources */,
|
||||
B5931B46251248B0007DA58E /* Advanced.EvolutionDemo.V3.FromV2.swift in Sources */,
|
||||
B5931B4A25124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift in Sources */,
|
||||
B5A5440D2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift in Sources */,
|
||||
B5931B4825124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift in Sources */,
|
||||
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */,
|
||||
B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */,
|
||||
B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */,
|
||||
B5A3919A24E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */,
|
||||
B5A3919624E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */,
|
||||
B5A3919424E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift in Sources */,
|
||||
B5A391AE24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */,
|
||||
B5A391A224E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
B5A3912724E5429300E7E8BD /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
B5A3912824E5429300E7E8BD /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
B5A3911324E5424E00E7E8BD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B5A3911424E5424E00E7E8BD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B5A3912C24E5429300E7E8BD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 2JT32EJ5BH;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.Demo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B5A3912D24E5429300E7E8BD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 2JT32EJ5BH;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.Demo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
B5A3911224E5424E00E7E8BD /* Build configuration list for PBXProject "Demo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B5A3911324E5424E00E7E8BD /* Debug */,
|
||||
B5A3911424E5424E00E7E8BD /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
B5A3912B24E5429300E7E8BD /* Build configuration list for PBXNativeTarget "Demo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B5A3912C24E5429300E7E8BD /* Debug */,
|
||||
B5A3912D24E5429300E7E8BD /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
B5A543D924FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
B5A543DA24FB7513000DC5E3 /* ColorsDemo.xcdatamodel */,
|
||||
);
|
||||
currentVersion = B5A543DA24FB7513000DC5E3 /* ColorsDemo.xcdatamodel */;
|
||||
path = Classic.ColorsDemo.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
B5D6F20E250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
B5D6F211250E1E7000DF5D2F /* Advanced.EvolutionDemo.V2.xcdatamodel */,
|
||||
B5D6F20F250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodel */,
|
||||
);
|
||||
currentVersion = B5D6F20F250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodel */;
|
||||
path = Advanced.EvolutionDemo.V1.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
/* End XCVersionGroup section */
|
||||
};
|
||||
rootObject = B5A3910F24E5424E00E7E8BD /* Project object */;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
LastUpgradeVersion = "1160"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,10 +14,10 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
BlueprintIdentifier = "B5A3911824E5429200E7E8BD"
|
||||
BuildableName = "Demo.app"
|
||||
BlueprintName = "Demo"
|
||||
ReferencedContainer = "container:Demo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
@@ -27,15 +27,6 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
@@ -53,10 +44,10 @@
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
BlueprintIdentifier = "B5A3911824E5429200E7E8BD"
|
||||
BuildableName = "Demo.app"
|
||||
BlueprintName = "Demo"
|
||||
ReferencedContainer = "container:Demo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
@@ -70,10 +61,10 @@
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
BlueprintIdentifier = "B5A3911824E5429200E7E8BD"
|
||||
BuildableName = "Demo.app"
|
||||
BlueprintName = "Demo"
|
||||
ReferencedContainer = "container:Demo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
70
Demo/Info.plist
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarTintParameters</key>
|
||||
<dict>
|
||||
<key>UINavigationBar</key>
|
||||
<dict>
|
||||
<key>Style</key>
|
||||
<string>UIBarStyleDefault</string>
|
||||
<key>Translucent</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
42
Demo/Rakefile
Normal file
@@ -0,0 +1,42 @@
|
||||
# coding: utf-8
|
||||
|
||||
task :format do
|
||||
|
||||
require 'xcodeproj'
|
||||
require 'fileutils'
|
||||
|
||||
project_path = 'Demo.xcodeproj'
|
||||
ignore_targets = []
|
||||
|
||||
# http://www.rubydoc.info/github/CocoaPods/Xcodeproj
|
||||
project = Xcodeproj::Project.open(project_path)
|
||||
validTargets = project.targets.select { |target| target.respond_to?(:product_type) }
|
||||
|
||||
validTargets.each do |target|
|
||||
if ignore_targets.include?(target.display_name)
|
||||
next
|
||||
end
|
||||
case target.product_type
|
||||
|
||||
when 'com.apple.product-type.application', 'com.apple.product-type.framework'
|
||||
target.source_build_phase.files.sort! do |f1, f2|
|
||||
result = (f1.display_name.count "+") <=> (f2.display_name.count "+")
|
||||
if result != 0
|
||||
next -result
|
||||
end
|
||||
result = (f1.display_name.count "-") <=> (f2.display_name.count "-")
|
||||
if result != 0
|
||||
next -result
|
||||
end
|
||||
result = (f1.display_name.count ".") <=> (f2.display_name.count ".")
|
||||
if result != 0
|
||||
next result
|
||||
end
|
||||
(f1.display_name <=> f2.display_name)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
project.save()
|
||||
end
|
||||
60
Demo/Resources/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController interfaceStyle="dark" id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Bp2-lt-3DL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2020 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="Yn3-8H-uzI">
|
||||
<rect key="frame" x="20" y="827" width="374" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="CoreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="IrK-8p-pit">
|
||||
<rect key="frame" x="122" y="228.5" width="170" height="170"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="IrK-8p-pit" secondAttribute="height" multiplier="1:1" id="WaM-8F-33r"/>
|
||||
<constraint firstAttribute="width" constant="170" id="dlo-1N-ikz"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="8Vu-0U-3hd">
|
||||
<rect key="frame" x="20" y="418.5" width="374" height="57.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Yn3-8H-uzI" firstAttribute="leading" secondItem="Bp2-lt-3DL" secondAttribute="leading" constant="20" symbolic="YES" id="7Dq-xP-k2v"/>
|
||||
<constraint firstItem="IrK-8p-pit" firstAttribute="centerY" secondItem="Bp2-lt-3DL" secondAttribute="centerY" multiplier="0.7" id="HUz-XL-l27"/>
|
||||
<constraint firstItem="IrK-8p-pit" firstAttribute="centerX" secondItem="Bp2-lt-3DL" secondAttribute="centerX" id="TSf-yM-hl1"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8Vu-0U-3hd" secondAttribute="centerX" id="TX2-HT-cKs"/>
|
||||
<constraint firstItem="Z3i-EZ-UGs" firstAttribute="bottom" secondItem="Yn3-8H-uzI" secondAttribute="bottom" constant="14" id="hAb-SJ-Qnm"/>
|
||||
<constraint firstAttribute="centerX" secondItem="Yn3-8H-uzI" secondAttribute="centerX" id="pNf-eo-RXZ"/>
|
||||
<constraint firstItem="8Vu-0U-3hd" firstAttribute="leading" secondItem="Bp2-lt-3DL" secondAttribute="leading" constant="20" symbolic="YES" id="pef-yD-C5e"/>
|
||||
<constraint firstItem="8Vu-0U-3hd" firstAttribute="top" secondItem="IrK-8p-pit" secondAttribute="bottom" constant="20" id="xQP-tq-hNL"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Z3i-EZ-UGs"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="CoreStoreIcon" width="170" height="170"/>
|
||||
</resources>
|
||||
</document>
|
||||
115
Demo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-60@3x-1.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-83.5@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "Mask + Oval 1 + Oval 1 + Oval 1.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"idiom" : "car",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-60@3x.png",
|
||||
"idiom" : "car",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
6
Demo/Resources/Images.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
15
Demo/Resources/Images.xcassets/CoreStoreIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "CoreStoreIcon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
||||
BIN
Demo/Resources/Images.xcassets/CoreStoreIcon.imageset/CoreStoreIcon.pdf
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
33
Demo/⭐️Sources/AppDelegate.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: - AppDelegate
|
||||
|
||||
@UIApplicationMain
|
||||
@objc final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
// MARK: UIApplicationDelegate
|
||||
|
||||
@objc dynamic func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc dynamic func application(
|
||||
_ application: UIApplication,
|
||||
configurationForConnecting connectingSceneSession: UISceneSession,
|
||||
options: UIScene.ConnectionOptions
|
||||
) -> UISceneConfiguration {
|
||||
|
||||
return UISceneConfiguration(
|
||||
name: "Default Configuration",
|
||||
sessionRole: connectingSceneSession.role
|
||||
)
|
||||
}
|
||||
}
|
||||
69
Demo/⭐️Sources/Helpers/ImageDownloader.swift
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
// MARK: - ImageDownloader
|
||||
|
||||
final class ImageDownloader: ObservableObject {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
private(set) var image: UIImage?
|
||||
|
||||
let url: URL?
|
||||
|
||||
init(url: URL?) {
|
||||
|
||||
self.url = url
|
||||
guard let url = url else {
|
||||
|
||||
return
|
||||
}
|
||||
if let image = Self.cache[url] {
|
||||
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
|
||||
func fetchImage(completion: @escaping (UIImage) -> Void = { _ in }) {
|
||||
|
||||
guard let url = url else {
|
||||
|
||||
return
|
||||
}
|
||||
if let image = Self.cache[url] {
|
||||
|
||||
self.objectWillChange.send()
|
||||
self.image = image
|
||||
completion(image)
|
||||
return
|
||||
}
|
||||
self.cancellable = URLSession.shared
|
||||
.dataTaskPublisher(for: url)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { output in
|
||||
|
||||
if let image = UIImage(data: output.data) {
|
||||
|
||||
Self.cache[url] = image
|
||||
self.objectWillChange.send()
|
||||
self.image = image
|
||||
completion(image)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static var cache: [URL: UIImage] = [:]
|
||||
|
||||
private var cancellable: AnyCancellable?
|
||||
}
|
||||
58
Demo/⭐️Sources/Helpers/InstructionsView.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - InstructionsView
|
||||
|
||||
struct InstructionsView: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(_ rows: (header: String, description: String)...) {
|
||||
|
||||
self.rows = rows.map({ .init(header: $0, description: $1) })
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .center) {
|
||||
Color.white
|
||||
.cornerRadius(10)
|
||||
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
ForEach(self.rows, id: \.header) { row in
|
||||
HStack(alignment: .firstTextBaseline, spacing: 5) {
|
||||
Text(row.header)
|
||||
.font(.callout)
|
||||
.fontWeight(.bold)
|
||||
Text(row.description)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
}
|
||||
.foregroundColor(Color(.sRGB, white: 0, opacity: 0.8))
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let rows: [InstructionsView.Row]
|
||||
|
||||
|
||||
// MARK: - Row
|
||||
|
||||
struct Row: Hashable {
|
||||
|
||||
// MARK: Internal
|
||||
let header: String
|
||||
let description: String
|
||||
}
|
||||
}
|
||||
|
||||
29
Demo/⭐️Sources/Helpers/LazyView.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - LazyView
|
||||
|
||||
struct LazyView<Content: View>: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(_ load: @escaping () -> Content) {
|
||||
|
||||
self.load = load
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: Content {
|
||||
|
||||
self.load()
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let load: () -> Content
|
||||
}
|
||||
71
Demo/⭐️Sources/Helpers/Menu/Menu.ItemView.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Menu
|
||||
|
||||
extension Menu {
|
||||
|
||||
// MARK: - Menu.ItemView
|
||||
|
||||
struct ItemView<Destination: View>: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
title: String,
|
||||
subtitle: String? = nil,
|
||||
destination: @escaping () -> Destination
|
||||
) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.destination = destination
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(destination: LazyView(self.destination)) {
|
||||
VStack(alignment: .leading) {
|
||||
Text(self.title)
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
self.subtitle.map {
|
||||
Text($0)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let title: String
|
||||
fileprivate let subtitle: String?
|
||||
fileprivate let destination: () -> Destination
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Menu_ItemView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
Menu.ItemView(
|
||||
title: "Item Title",
|
||||
subtitle: "A subtitle caption for this item",
|
||||
destination: {
|
||||
Color.blue
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
132
Demo/⭐️Sources/Helpers/Menu/Menu.MainView.swift
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
// MARK: - Menu
|
||||
|
||||
extension Menu {
|
||||
|
||||
// MARK: - Menu.MainView
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section(header: Text("Modern (CoreStoreObject subclasses)")) {
|
||||
Menu.ItemView(
|
||||
title: "Placemarks",
|
||||
subtitle: "Making changes using Transactions",
|
||||
destination: {
|
||||
Modern.PlacemarksDemo.MainView()
|
||||
}
|
||||
)
|
||||
Menu.ItemView(
|
||||
title: "Time Zones",
|
||||
subtitle: "Fetching objects and Querying raw values",
|
||||
destination: {
|
||||
Modern.TimeZonesDemo.MainView()
|
||||
}
|
||||
)
|
||||
Menu.ItemView(
|
||||
title: "Colors (UIKit)",
|
||||
subtitle: "Observing list changes and single-object changes using DiffableDataSources",
|
||||
destination: {
|
||||
Modern.ColorsDemo.MainView(
|
||||
listView: { listPublisher, onPaletteTapped in
|
||||
Modern.ColorsDemo.UIKit.ListView(
|
||||
listPublisher: listPublisher,
|
||||
onPaletteTapped: onPaletteTapped
|
||||
)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
},
|
||||
detailView: { objectPublisher in
|
||||
Modern.ColorsDemo.UIKit.DetailView(objectPublisher)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
Menu.ItemView(
|
||||
title: "Colors (SwiftUI)",
|
||||
subtitle: "Observing list changes and single-object changes using SwiftUI bindings",
|
||||
destination: {
|
||||
Modern.ColorsDemo.MainView(
|
||||
listView: { listPublisher, onPaletteTapped in
|
||||
Modern.ColorsDemo.SwiftUI.ListView(
|
||||
listPublisher: listPublisher,
|
||||
onPaletteTapped: onPaletteTapped
|
||||
)
|
||||
},
|
||||
detailView: { objectPublisher in
|
||||
Modern.ColorsDemo.SwiftUI.DetailView(objectPublisher)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
Menu.ItemView(
|
||||
title: "Pokedex API",
|
||||
subtitle: "Importing JSON data from external source",
|
||||
destination: {
|
||||
Modern.PokedexDemo.MainView()
|
||||
}
|
||||
)
|
||||
}
|
||||
Section(header: Text("Classic (NSManagedObject subclasses)")) {
|
||||
Menu.ItemView(
|
||||
title: "Colors",
|
||||
subtitle: "Observing list changes and single-object changes using ListMonitor",
|
||||
destination: {
|
||||
Classic.ColorsDemo.MainView()
|
||||
}
|
||||
)
|
||||
}
|
||||
Section(header: Text("Advanced")) {
|
||||
Menu.ItemView(
|
||||
title: "Accounts",
|
||||
subtitle: "Switching between multiple persistent stores",
|
||||
destination: { EmptyView() }
|
||||
)
|
||||
.disabled(true)
|
||||
Menu.ItemView(
|
||||
title: "Evolution",
|
||||
subtitle: "Migrating and reverse-migrating stores",
|
||||
destination: {
|
||||
Advanced.EvolutionDemo.MainView()
|
||||
}
|
||||
)
|
||||
Menu.ItemView(
|
||||
title: "Logger",
|
||||
subtitle: "Implementing a custom logger",
|
||||
destination: { EmptyView() }
|
||||
)
|
||||
.disabled(true)
|
||||
}
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationBarTitle("CoreStore Demos")
|
||||
Menu.PlaceholderView()
|
||||
}
|
||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Menu_MainView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
Menu.MainView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
44
Demo/⭐️Sources/Helpers/Menu/Menu.PlaceholderView.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Menu
|
||||
|
||||
extension Menu {
|
||||
|
||||
// MARK: - Menu.PlaceholderView
|
||||
|
||||
struct PlaceholderView: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: UIViewControllerRepresentable
|
||||
|
||||
typealias UIViewControllerType = UIViewController
|
||||
|
||||
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
|
||||
|
||||
return UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Menu_PlaceholderView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
return Menu.PlaceholderView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
10
Demo/⭐️Sources/Helpers/Menu/Menu.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Menu
|
||||
|
||||
enum Menu {}
|
||||
36
Demo/⭐️Sources/SceneDelegate.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// MARK: - SceneDelegate
|
||||
|
||||
@objc final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
// MARK: UIWindowSceneDelegate
|
||||
|
||||
@objc dynamic var window: UIWindow?
|
||||
|
||||
|
||||
// MARK: UISceneDelegate
|
||||
|
||||
@objc dynamic func scene(
|
||||
_ scene: UIScene,
|
||||
willConnectTo session: UISceneSession,
|
||||
options connectionOptions: UIScene.ConnectionOptions
|
||||
) {
|
||||
|
||||
guard case let scene as UIWindowScene = scene else {
|
||||
|
||||
return
|
||||
}
|
||||
let window = UIWindow(windowScene: scene)
|
||||
window.rootViewController = UIHostingController(
|
||||
rootView: Menu.MainView()
|
||||
)
|
||||
self.window = window
|
||||
window.makeKeyAndVisible()
|
||||
}
|
||||
}
|
||||
10
Demo/⭐️Sources/⭐️Demos/⭐️Advanced/Advanced.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
// MARK: - Advanced
|
||||
|
||||
/**
|
||||
Sample application of complex use cases
|
||||
*/
|
||||
enum Advanced {}
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
typealias CreatureType = Advanced_EvolutionDemo_CreatureType
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
protocol Advanced_EvolutionDemo_CreatureType: DynamicObject, CustomStringConvertible {
|
||||
|
||||
var dnaCode: Int64 { get set }
|
||||
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Self
|
||||
|
||||
func mutate(in transaction: BaseDataTransaction)
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import Combine
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.CreaturesDataSource
|
||||
|
||||
/**
|
||||
A type-erasing adapter to support different `ListPublisher` types
|
||||
*/
|
||||
final class CreaturesDataSource: ObservableObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init<T: NSManagedObject & Advanced.EvolutionDemo.CreatureType>(
|
||||
listPublisher: ListPublisher<T>,
|
||||
dataStack: DataStack
|
||||
) {
|
||||
|
||||
self.numberOfItems = {
|
||||
listPublisher.snapshot.numberOfItems
|
||||
}
|
||||
self.itemDescriptionAtIndex = { index in
|
||||
listPublisher.snapshot[index].object?.description
|
||||
}
|
||||
self.addItems = { count in
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
let nextDNACode = try transaction.fetchCount(From<T>())
|
||||
for offset in 0 ..< count {
|
||||
|
||||
let object = transaction.create(Into<T>())
|
||||
object.dnaCode = .init(nextDNACode + offset)
|
||||
object.mutate(in: transaction)
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
self.mutateItemAtIndex = { index in
|
||||
|
||||
let object = listPublisher.snapshot[index]
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
object
|
||||
.asEditable(in: transaction)?
|
||||
.mutate(in: transaction)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
self.deleteAllItems = {
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
try transaction.deleteAll(From<T>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
init<T: CoreStoreObject & Advanced.EvolutionDemo.CreatureType>(
|
||||
listPublisher: ListPublisher<T>,
|
||||
dataStack: DataStack
|
||||
) {
|
||||
|
||||
self.numberOfItems = {
|
||||
listPublisher.snapshot.numberOfItems
|
||||
}
|
||||
self.itemDescriptionAtIndex = { index in
|
||||
listPublisher.snapshot[index].object?.description
|
||||
}
|
||||
self.addItems = { count in
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
let nextDNACode = try transaction.fetchCount(From<T>())
|
||||
for offset in 0 ..< count {
|
||||
|
||||
let object = transaction.create(Into<T>())
|
||||
object.dnaCode = .init(nextDNACode + offset)
|
||||
object.mutate(in: transaction)
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
self.mutateItemAtIndex = { index in
|
||||
|
||||
let object = listPublisher.snapshot[index]
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
object
|
||||
.asEditable(in: transaction)?
|
||||
.mutate(in: transaction)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
self.deleteAllItems = {
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
try transaction.deleteAll(From<T>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
func numberOfCreatures() -> Int {
|
||||
|
||||
return self.numberOfItems()
|
||||
}
|
||||
|
||||
func creatureDescription(at index: Int) -> String? {
|
||||
|
||||
return self.itemDescriptionAtIndex(index)
|
||||
}
|
||||
|
||||
func mutate(at index: Int) {
|
||||
|
||||
self.mutateItemAtIndex(index)
|
||||
}
|
||||
|
||||
func add(count: Int) {
|
||||
|
||||
self.addItems(count)
|
||||
}
|
||||
|
||||
func clear() {
|
||||
|
||||
self.deleteAllItems()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let numberOfItems: () -> Int
|
||||
private let itemDescriptionAtIndex: (Int) -> String?
|
||||
private let mutateItemAtIndex: (Int) -> Void
|
||||
private let addItems: (Int) -> Void
|
||||
private let deleteAllItems: () -> Void
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - AdvancedEvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - GeologicalPeriod
|
||||
|
||||
enum GeologicalPeriod: RawRepresentable, CaseIterable, Hashable, CustomStringConvertible {
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
case ageOfInvertebrates
|
||||
case ageOfFishes
|
||||
case ageOfReptiles
|
||||
case ageOfMammals
|
||||
|
||||
var version: ModelVersion {
|
||||
|
||||
return self.rawValue
|
||||
}
|
||||
|
||||
var creatureType: Advanced.EvolutionDemo.CreatureType.Type {
|
||||
|
||||
switch self {
|
||||
|
||||
case .ageOfInvertebrates:
|
||||
return Advanced.EvolutionDemo.V1.Creature.self
|
||||
case .ageOfFishes:
|
||||
return Advanced.EvolutionDemo.V2.Creature.self
|
||||
case .ageOfReptiles:
|
||||
return Advanced.EvolutionDemo.V3.Creature.self
|
||||
case .ageOfMammals:
|
||||
return Advanced.EvolutionDemo.V4.Creature.self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
var description: String {
|
||||
|
||||
switch self {
|
||||
|
||||
case .ageOfInvertebrates:
|
||||
return "Invertebrates"
|
||||
case .ageOfFishes:
|
||||
return "Fishes"
|
||||
case .ageOfReptiles:
|
||||
return "Reptiles"
|
||||
case .ageOfMammals:
|
||||
return "Mammals"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: RawRepresentable
|
||||
|
||||
typealias RawValue = ModelVersion
|
||||
|
||||
var rawValue: ModelVersion {
|
||||
|
||||
switch self {
|
||||
|
||||
case .ageOfInvertebrates:
|
||||
return Advanced.EvolutionDemo.V1.name
|
||||
case .ageOfFishes:
|
||||
return Advanced.EvolutionDemo.V2.name
|
||||
case .ageOfReptiles:
|
||||
return Advanced.EvolutionDemo.V3.name
|
||||
case .ageOfMammals:
|
||||
return Advanced.EvolutionDemo.V4.name
|
||||
}
|
||||
}
|
||||
|
||||
init?(rawValue: ModelVersion) {
|
||||
|
||||
switch rawValue {
|
||||
|
||||
case Advanced.EvolutionDemo.V1.name:
|
||||
self = .ageOfInvertebrates
|
||||
case Advanced.EvolutionDemo.V2.name:
|
||||
self = .ageOfFishes
|
||||
case Advanced.EvolutionDemo.V3.name:
|
||||
self = .ageOfReptiles
|
||||
case Advanced.EvolutionDemo.V4.name:
|
||||
self = .ageOfMammals
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.ItemView
|
||||
|
||||
struct ItemView: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(description: String?, mutate: @escaping () -> Void) {
|
||||
|
||||
self.description = description
|
||||
self.mutate = mutate
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(self.description ?? "")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Button(
|
||||
action: self.mutate,
|
||||
label: {
|
||||
Text("Mutate")
|
||||
.foregroundColor(.accentColor)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.disabled(self.description == nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let description: String?
|
||||
fileprivate let mutate: () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_ItemView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
Advanced.EvolutionDemo.ItemView(
|
||||
description: """
|
||||
dnaCode: 123
|
||||
numberOfLimbs: 4
|
||||
hasVertebrae: true
|
||||
hasHead: true
|
||||
hasTail: true
|
||||
habitat: land
|
||||
hasWings: false
|
||||
""",
|
||||
mutate: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.ListView
|
||||
|
||||
struct ListView: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
let dataSource = self.dataSource
|
||||
return List {
|
||||
ForEach(0 ..< dataSource.numberOfCreatures(), id: \.self) { (index) in
|
||||
Advanced.EvolutionDemo.ItemView(
|
||||
description: dataSource.creatureDescription(at: index),
|
||||
mutate: {
|
||||
|
||||
dataSource.mutate(at: index)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
dataStack: DataStack,
|
||||
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
) {
|
||||
|
||||
self.period = period
|
||||
self.dataStack = dataStack
|
||||
self.dataSource = dataSource
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let period: Advanced.EvolutionDemo.GeologicalPeriod
|
||||
|
||||
private let dataStack: DataStack
|
||||
|
||||
@ObservedObject
|
||||
private var dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_ListView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
let dataStack = DataStack(
|
||||
CoreStoreSchema(
|
||||
modelVersion: Advanced.EvolutionDemo.V4.name,
|
||||
entities: [
|
||||
Entity<Advanced.EvolutionDemo.V4.Creature>("Creature")
|
||||
]
|
||||
)
|
||||
)
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(fileName: "Advanced.EvolutionDemo.ListView.Preview.sqlite")
|
||||
)
|
||||
try! dataStack.perform(
|
||||
synchronous: { transaction in
|
||||
|
||||
for dnaCode in 0 ..< 10 as Range<Int64> {
|
||||
|
||||
let object = transaction.create(Into<Advanced.EvolutionDemo.V4.Creature>())
|
||||
object.dnaCode = dnaCode
|
||||
object.mutate(in: transaction)
|
||||
}
|
||||
}
|
||||
)
|
||||
return Advanced.EvolutionDemo.ListView(
|
||||
period: .ageOfMammals,
|
||||
dataStack: dataStack,
|
||||
dataSource: Advanced.EvolutionDemo.V4.Creature.dataSource(in: dataStack)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.MainView
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
let migrator = self.migrator
|
||||
let listView: AnyView
|
||||
if let current = migrator.current {
|
||||
|
||||
listView = AnyView(
|
||||
Advanced.EvolutionDemo.ListView(
|
||||
period: current.period,
|
||||
dataStack: current.dataStack,
|
||||
dataSource: current.dataSource
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
listView = AnyView(
|
||||
Advanced.EvolutionDemo.ProgressView(progress: migrator.progress)
|
||||
)
|
||||
}
|
||||
|
||||
return VStack(spacing: 0) {
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
Text("Age of")
|
||||
.padding(.trailing)
|
||||
Picker(selection: self.$migrator.currentPeriod, label: EmptyView()) {
|
||||
ForEach(Advanced.EvolutionDemo.GeologicalPeriod.allCases, id: \.self) { period in
|
||||
Text(period.description).tag(period)
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
}
|
||||
.padding()
|
||||
listView
|
||||
.edgesIgnoringSafeArea(.vertical)
|
||||
}
|
||||
.navigationBarTitle("Evolution")
|
||||
.disabled(migrator.isBusy || migrator.current == nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
private var migrator: Advanced.EvolutionDemo.Migrator = .init()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_MainView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
Advanced.EvolutionDemo.MainView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.ProgressView
|
||||
|
||||
struct ProgressView: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(progress: Progress?) {
|
||||
|
||||
self.progressObserver = .init(progress)
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
|
||||
guard self.progressObserver.isMigrating else {
|
||||
|
||||
return AnyView(
|
||||
VStack(alignment: .center) {
|
||||
Text("Preparing creatures...")
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
)
|
||||
}
|
||||
return AnyView(
|
||||
VStack(alignment: .leading) {
|
||||
Text("Migrating: \(self.progressObserver.localizedDescription)")
|
||||
.font(.headline)
|
||||
.padding([.top, .horizontal])
|
||||
Text("Progressive step: \(self.progressObserver.localizedAdditionalDescription)")
|
||||
.font(.subheadline)
|
||||
.padding(.horizontal)
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .leading) {
|
||||
Color.gray
|
||||
.opacity(0.2)
|
||||
.frame(width: geometry.size.width, height: 8)
|
||||
.cornerRadius(4.0)
|
||||
Color.blue
|
||||
.frame(
|
||||
width: geometry.size.width
|
||||
* self.progressObserver.fractionCompleted,
|
||||
height: 8
|
||||
)
|
||||
.cornerRadius(4.0)
|
||||
}
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@ObservedObject
|
||||
private var progressObserver: ProgressObserver
|
||||
|
||||
|
||||
// MARK: - ProgressObserver
|
||||
|
||||
fileprivate final class ProgressObserver: ObservableObject {
|
||||
|
||||
private(set) var fractionCompleted: CGFloat = 0
|
||||
private(set) var localizedDescription: String = ""
|
||||
private(set) var localizedAdditionalDescription: String = ""
|
||||
|
||||
var isMigrating: Bool {
|
||||
|
||||
return self.progress != nil
|
||||
}
|
||||
|
||||
init(_ progress: Progress?) {
|
||||
|
||||
self.progress = progress
|
||||
|
||||
progress?.setProgressHandler { [weak self] (progess) in
|
||||
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.objectWillChange.send()
|
||||
self.fractionCompleted = CGFloat(progress?.fractionCompleted ?? 0)
|
||||
self.localizedDescription = progress?.localizedDescription ?? ""
|
||||
self.localizedAdditionalDescription = progress?.localizedAdditionalDescription ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let progress: Progress?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Advanced_EvolutionDemo_ProgressView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
let progress = Progress(totalUnitCount: 10)
|
||||
progress.completedUnitCount = 3
|
||||
return Advanced.EvolutionDemo.ProgressView(
|
||||
progress: progress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V1.Creature
|
||||
|
||||
@objc(Advanced_EvolutionDemo_V1_Creature)
|
||||
final class Advanced_EvolutionDemo_V1_Creature: NSManagedObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
@NSManaged
|
||||
dynamic var dnaCode: Int64
|
||||
|
||||
@NSManaged
|
||||
dynamic var numberOfFlagella: Int32
|
||||
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
override var description: String {
|
||||
|
||||
return """
|
||||
dnaCode: \(self.dnaCode)
|
||||
numberOfFlagella: \(self.numberOfFlagella)
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V1.Creature>()
|
||||
.orderBy(.descending(\.dnaCode))
|
||||
),
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V1.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V1.Creature {
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V1.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
func mutate(in transaction: BaseDataTransaction) {
|
||||
|
||||
self.numberOfFlagella = .random(in: 1...200)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V1
|
||||
|
||||
extension Advanced.EvolutionDemo.V1 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V1.FromV2
|
||||
|
||||
enum FromV2 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static var mapping: XcodeSchemaMappingProvider {
|
||||
|
||||
return XcodeSchemaMappingProvider(
|
||||
from: Advanced.EvolutionDemo.V2.name,
|
||||
to: Advanced.EvolutionDemo.V1.name,
|
||||
mappingModelBundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V1
|
||||
|
||||
/**
|
||||
Namespace for V1 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfInvertebrates`)
|
||||
*/
|
||||
enum V1 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let name: ModelVersion = "Advanced.EvolutionDemo.V1"
|
||||
|
||||
typealias Creature = Advanced_EvolutionDemo_V1_Creature
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
|
||||
<true/>
|
||||
<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
|
||||
<true/>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Advanced.EvolutionDemo.V1.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V1_Creature" syncable="YES">
|
||||
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numberOfFlagella" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Creature" positionX="-27" positionY="18" width="128" height="73"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V2_Creature" syncable="YES">
|
||||
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hasHead" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hasTail" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hasVertebrae" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Creature" positionX="-9" positionY="36" width="128" height="118"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2.Creature
|
||||
|
||||
@objc(Advanced_EvolutionDemo_V2_Creature)
|
||||
final class Advanced_EvolutionDemo_V2_Creature: NSManagedObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
@NSManaged
|
||||
dynamic var dnaCode: Int64
|
||||
|
||||
@NSManaged
|
||||
dynamic var numberOfFlippers: Int32
|
||||
|
||||
@NSManaged
|
||||
dynamic var hasVertebrae: Bool
|
||||
|
||||
@NSManaged
|
||||
dynamic var hasHead: Bool
|
||||
|
||||
@NSManaged
|
||||
dynamic var hasTail: Bool
|
||||
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
override var description: String {
|
||||
|
||||
return """
|
||||
dnaCode: \(self.dnaCode)
|
||||
numberOfFlippers: \(self.numberOfFlippers)
|
||||
hasVertebrae: \(self.hasVertebrae)
|
||||
hasHead: \(self.hasHead)
|
||||
hasTail: \(self.hasTail)
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V2.Creature>()
|
||||
.orderBy(.descending(\.dnaCode))
|
||||
),
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V2.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V2.Creature {
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V2.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
func mutate(in transaction: BaseDataTransaction) {
|
||||
|
||||
self.numberOfFlippers = .random(in: 1...4) * 2
|
||||
self.hasVertebrae = .random()
|
||||
self.hasHead = true
|
||||
self.hasTail = .random()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2
|
||||
|
||||
extension Advanced.EvolutionDemo.V2 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2.FromV1
|
||||
|
||||
enum FromV1 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static var mapping: XcodeSchemaMappingProvider {
|
||||
|
||||
return XcodeSchemaMappingProvider(
|
||||
from: Advanced.EvolutionDemo.V1.name,
|
||||
to: Advanced.EvolutionDemo.V2.name,
|
||||
mappingModelBundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreData
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2.FromV1MigrationPolicy
|
||||
|
||||
@objc(Advanced_EvolutionDemo_V2_FromV1MigrationPolicy)
|
||||
final class Advanced_EvolutionDemo_V2_FromV1MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
// MARK: NSEntityMigrationPolicy
|
||||
|
||||
override func createDestinationInstances(
|
||||
forSource sInstance: NSManagedObject,
|
||||
in mapping: NSEntityMapping,
|
||||
manager: NSMigrationManager
|
||||
) throws {
|
||||
|
||||
try super.createDestinationInstances(
|
||||
forSource: sInstance,
|
||||
in: mapping,
|
||||
manager: manager
|
||||
)
|
||||
|
||||
for dInstance in manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]) {
|
||||
|
||||
dInstance.setValue(
|
||||
Bool.random(),
|
||||
forKey: #keyPath(Advanced.EvolutionDemo.V2.Creature.hasVertebrae)
|
||||
)
|
||||
dInstance.setValue(
|
||||
Bool.random(),
|
||||
forKey: #keyPath(Advanced.EvolutionDemo.V2.Creature.hasTail)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2
|
||||
|
||||
extension Advanced.EvolutionDemo.V2 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2.FromV3
|
||||
|
||||
enum FromV3 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static var mapping: CustomSchemaMappingProvider {
|
||||
|
||||
return CustomSchemaMappingProvider(
|
||||
from: Advanced.EvolutionDemo.V3.name,
|
||||
to: Advanced.EvolutionDemo.V2.name,
|
||||
entityMappings: [
|
||||
.transformEntity(
|
||||
sourceEntity: "Creature",
|
||||
destinationEntity: "Creature",
|
||||
transformer: { (source, createDestination) in
|
||||
|
||||
let destination = createDestination()
|
||||
destination["dnaCode"] = source["dnaCode"]
|
||||
destination["numberOfFlippers"] = source["numberOfLimbs"]
|
||||
destination["hasVertebrae"] = source["hasVertebrae"]
|
||||
destination["hasHead"] = source["hasHead"]
|
||||
destination["hasTail"] = source["hasTail"]
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V2
|
||||
|
||||
/**
|
||||
Namespace for V2 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfFishes`)
|
||||
*/
|
||||
enum V2 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let name: ModelVersion = "Advanced.EvolutionDemo.V2"
|
||||
|
||||
typealias Creature = Advanced_EvolutionDemo_V2_Creature
|
||||
|
||||
typealias FromV1MigrationPolicy = Advanced_EvolutionDemo_V2_FromV1MigrationPolicy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V2_Creature" syncable="YES">
|
||||
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hasHead" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hasTail" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hasVertebrae" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Creature" positionX="-45" positionY="0" width="128" height="118"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3
|
||||
|
||||
extension Advanced.EvolutionDemo.V3 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3.Creature
|
||||
|
||||
final class Creature: CoreStoreObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("dnaCode")
|
||||
var dnaCode: Int64 = 0
|
||||
|
||||
@Field.Stored("numberOfLimbs")
|
||||
var numberOfLimbs: Int32 = 0
|
||||
|
||||
@Field.Stored("hasVertebrae")
|
||||
var hasVertebrae: Bool = false
|
||||
|
||||
@Field.Stored("hasHead")
|
||||
var hasHead: Bool = true
|
||||
|
||||
@Field.Stored("hasTail")
|
||||
var hasTail: Bool = true
|
||||
|
||||
@Field.Stored("hasWings")
|
||||
var hasWings: Bool = false
|
||||
|
||||
@Field.Stored("habitat")
|
||||
var habitat: Habitat = .water
|
||||
|
||||
|
||||
// MARK: - Habitat
|
||||
|
||||
enum Habitat: String, CaseIterable, ImportableAttributeType, FieldStorableType {
|
||||
|
||||
case water = "water"
|
||||
case land = "land"
|
||||
case amphibian = "amphibian"
|
||||
}
|
||||
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
var description: String {
|
||||
|
||||
return """
|
||||
dnaCode: \(self.dnaCode)
|
||||
numberOfLimbs: \(self.numberOfLimbs)
|
||||
hasVertebrae: \(self.hasVertebrae)
|
||||
hasHead: \(self.hasHead)
|
||||
hasTail: \(self.hasTail)
|
||||
habitat: \(self.habitat)
|
||||
hasWings: \(self.hasWings)
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V3.Creature>()
|
||||
.orderBy(.descending(\.$dnaCode))
|
||||
),
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V3.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V3.Creature {
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V3.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
func mutate(in transaction: BaseDataTransaction) {
|
||||
|
||||
self.numberOfLimbs = .random(in: 1...4) * 2
|
||||
self.hasVertebrae = .random()
|
||||
self.hasHead = true
|
||||
self.hasTail = .random()
|
||||
self.habitat = Habitat.allCases.randomElement()!
|
||||
self.hasWings = .random()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3
|
||||
|
||||
extension Advanced.EvolutionDemo.V3 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3.FromV2
|
||||
|
||||
enum FromV2 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static var mapping: CustomSchemaMappingProvider {
|
||||
|
||||
return CustomSchemaMappingProvider(
|
||||
from: Advanced.EvolutionDemo.V2.name,
|
||||
to: Advanced.EvolutionDemo.V3.name,
|
||||
entityMappings: [
|
||||
.transformEntity(
|
||||
sourceEntity: "Creature",
|
||||
destinationEntity: "Creature",
|
||||
transformer: { (source, createDestination) in
|
||||
|
||||
let destination = createDestination()
|
||||
destination["dnaCode"] = source["dnaCode"]
|
||||
destination["numberOfLimbs"] = source["numberOfFlippers"]
|
||||
destination["hasVertebrae"] = source["hasVertebrae"]
|
||||
destination["hasHead"] = source["hasHead"]
|
||||
destination["hasTail"] = source["hasTail"]
|
||||
destination["hasWings"] = Bool.random()
|
||||
destination["habitat"] = Advanced.EvolutionDemo.V3.Creature.Habitat.allCases.randomElement()!.rawValue
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3
|
||||
|
||||
extension Advanced.EvolutionDemo.V3 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3.FromV4
|
||||
|
||||
enum FromV4 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static var mapping: CustomSchemaMappingProvider {
|
||||
|
||||
return CustomSchemaMappingProvider(
|
||||
from: Advanced.EvolutionDemo.V4.name,
|
||||
to: Advanced.EvolutionDemo.V3.name,
|
||||
entityMappings: [
|
||||
.transformEntity(
|
||||
sourceEntity: "Creature",
|
||||
destinationEntity: "Creature",
|
||||
transformer: CustomSchemaMappingProvider.CustomMapping.inferredTransformation(_:_:)
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V3
|
||||
|
||||
/**
|
||||
Namespace for V3 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfReptiles`)
|
||||
*/
|
||||
enum V3 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let name: ModelVersion = "Advanced.EvolutionDemo.V3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V4
|
||||
|
||||
extension Advanced.EvolutionDemo.V4 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V4.Creature
|
||||
|
||||
final class Creature: CoreStoreObject, Advanced.EvolutionDemo.CreatureType {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("dnaCode")
|
||||
var dnaCode: Int64 = 0
|
||||
|
||||
@Field.Stored("numberOfLimbs")
|
||||
var numberOfLimbs: Int32 = 0
|
||||
|
||||
@Field.Stored("hasVertebrae")
|
||||
var hasVertebrae: Bool = false
|
||||
|
||||
@Field.Stored("hasHead")
|
||||
var hasHead: Bool = true
|
||||
|
||||
@Field.Stored("hasTail")
|
||||
var hasTail: Bool = false
|
||||
|
||||
@Field.Stored("hasWings")
|
||||
var hasWings: Bool = false
|
||||
|
||||
|
||||
typealias Habitat = Advanced.EvolutionDemo.V3.Creature.Habitat
|
||||
|
||||
@Field.Stored("habitat")
|
||||
var habitat: Habitat = .water
|
||||
|
||||
@Field.Stored("isWarmBlooded")
|
||||
var isWarmBlooded: Bool = true
|
||||
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
var description: String {
|
||||
|
||||
return """
|
||||
dnaCode: \(self.dnaCode)
|
||||
numberOfLimbs: \(self.numberOfLimbs)
|
||||
hasVertebrae: \(self.hasVertebrae)
|
||||
hasHead: \(self.hasHead)
|
||||
hasTail: \(self.hasTail)
|
||||
habitat: \(self.habitat)
|
||||
hasWings: \(self.hasWings)
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||
|
||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||
|
||||
return .init(
|
||||
listPublisher: dataStack.publishList(
|
||||
From<Advanced.EvolutionDemo.V4.Creature>()
|
||||
.orderBy(.descending(\.$dnaCode))
|
||||
),
|
||||
dataStack: dataStack
|
||||
)
|
||||
}
|
||||
|
||||
static func count(in transaction: BaseDataTransaction) throws -> Int {
|
||||
|
||||
return try transaction.fetchCount(
|
||||
From<Advanced.EvolutionDemo.V4.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V4.Creature {
|
||||
|
||||
return transaction.create(
|
||||
Into<Advanced.EvolutionDemo.V4.Creature>()
|
||||
)
|
||||
}
|
||||
|
||||
func mutate(in transaction: BaseDataTransaction) {
|
||||
|
||||
self.numberOfLimbs = .random(in: 1...4) * 2
|
||||
self.hasVertebrae = .random()
|
||||
self.hasHead = true
|
||||
self.hasTail = .random()
|
||||
self.habitat = Habitat.allCases.randomElement()!
|
||||
self.hasWings = .random()
|
||||
self.isWarmBlooded = .random()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V4
|
||||
|
||||
extension Advanced.EvolutionDemo.V4 {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V4.FromV3
|
||||
|
||||
enum FromV3 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static var mapping: CustomSchemaMappingProvider {
|
||||
|
||||
return CustomSchemaMappingProvider(
|
||||
from: Advanced.EvolutionDemo.V3.name,
|
||||
to: Advanced.EvolutionDemo.V4.name,
|
||||
entityMappings: [
|
||||
.transformEntity(
|
||||
sourceEntity: "Creature",
|
||||
destinationEntity: "Creature",
|
||||
transformer: { (source, createDestination) in
|
||||
|
||||
let destination = createDestination()
|
||||
destination.enumerateAttributes { (destinationAttribute, sourceAttribute) in
|
||||
|
||||
if let sourceAttribute = sourceAttribute {
|
||||
|
||||
destination[destinationAttribute] = source[sourceAttribute]
|
||||
}
|
||||
}
|
||||
destination["isWarmBlooded"] = Bool.random()
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.V4
|
||||
|
||||
/**
|
||||
Namespace for V3 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfMammals`)
|
||||
*/
|
||||
enum V4 {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let name: ModelVersion = "Advanced.EvolutionDemo.V4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
// MARK: - Advanced
|
||||
|
||||
extension Advanced {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
/**
|
||||
Sample execution of progressive migrations. This example demonstrates the following concepts:
|
||||
|
||||
- How to inspect the current model version of the store (if it exists)
|
||||
- How to do two-way migration chains (upgrades + downgrades)
|
||||
- How to support multiple versions of the model on the same app
|
||||
- How to migrate between `NSManagedObject` schema (`xcdatamodel` files) and `CoreStoreObject` schema.
|
||||
- How to use `XcodeSchemaMappingProvider`s for `NSManagedObject` stores, and `CustomSchemaMappingProvider`s for `CoreStoreObject` stores
|
||||
- How to manage migration models using namespacing technique
|
||||
|
||||
Note that ideally, your app should be supporting just the latest version of the model, and provide one-way progressive migrations from all the earlier versions.
|
||||
*/
|
||||
enum EvolutionDemo {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V1_Creature" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numberOfFlagella" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Creature" positionX="-36" positionY="9" width="128" height="73"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V1_Creature" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numberOfFlagella" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Creature" positionX="-36" positionY="9" width="128" height="73"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,260 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo
|
||||
|
||||
extension Advanced.EvolutionDemo {
|
||||
|
||||
// MARK: - Advanced.EvolutionDemo.Migrator
|
||||
|
||||
final class Migrator: ObservableObject {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Creating a complex `DataStack` that contains all schema histories. The `exactCurrentModelVersion` will specify the target version (if required), and `migrationChain` will provide the upgrade/downgrade progressive migration path.
|
||||
*/
|
||||
private func createDataStack(
|
||||
exactCurrentModelVersion: ModelVersion?,
|
||||
migrationChain: MigrationChain
|
||||
) -> DataStack {
|
||||
|
||||
let xcodeV1ToV2ModelSchema = XcodeDataModelSchema.from(
|
||||
modelName: "Advanced.EvolutionDemo.V1",
|
||||
bundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
|
||||
)
|
||||
return DataStack(
|
||||
schemaHistory: SchemaHistory(
|
||||
allSchema: xcodeV1ToV2ModelSchema.allSchema
|
||||
+ [
|
||||
CoreStoreSchema(
|
||||
modelVersion: Advanced.EvolutionDemo.V3.name,
|
||||
entities: [
|
||||
Entity<Advanced.EvolutionDemo.V3.Creature>("Creature")
|
||||
]
|
||||
),
|
||||
CoreStoreSchema(
|
||||
modelVersion: Advanced.EvolutionDemo.V4.name,
|
||||
entities: [
|
||||
Entity<Advanced.EvolutionDemo.V4.Creature>("Creature")
|
||||
]
|
||||
)
|
||||
],
|
||||
migrationChain: migrationChain,
|
||||
exactCurrentModelVersion: exactCurrentModelVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Creating a complex `SQLiteStore` that contains all schema mappings for both upgrade and downgrade cases.
|
||||
*/
|
||||
private func accessSQLiteStore() -> SQLiteStore {
|
||||
|
||||
let upgradeMappings: [SchemaMappingProvider] = [
|
||||
Advanced.EvolutionDemo.V2.FromV1.mapping,
|
||||
Advanced.EvolutionDemo.V3.FromV2.mapping,
|
||||
Advanced.EvolutionDemo.V4.FromV3.mapping
|
||||
]
|
||||
let downgradeMappings: [SchemaMappingProvider] = [
|
||||
Advanced.EvolutionDemo.V3.FromV4.mapping,
|
||||
Advanced.EvolutionDemo.V2.FromV3.mapping,
|
||||
Advanced.EvolutionDemo.V1.FromV2.mapping,
|
||||
]
|
||||
return SQLiteStore(
|
||||
fileName: "Advanced.EvolutionDemo.sqlite",
|
||||
configuration: nil,
|
||||
migrationMappingProviders: upgradeMappings + downgradeMappings,
|
||||
localStorageOptions: []
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: Find the model version used by an existing `SQLiteStore`, or just return the latest version if the store is not created yet.
|
||||
*/
|
||||
private func findCurrentVersion() -> ModelVersion {
|
||||
|
||||
let allVersions = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
||||
.map({ $0.version })
|
||||
|
||||
// Since we are only interested in finding current version, we'll assume an upgrading `MigrationChain`
|
||||
let dataStack = self.createDataStack(
|
||||
exactCurrentModelVersion: nil,
|
||||
migrationChain: MigrationChain(allVersions)
|
||||
)
|
||||
let migrations = try! dataStack.requiredMigrationsForStorage(
|
||||
self.accessSQLiteStore()
|
||||
)
|
||||
|
||||
// If no migrations are needed, it means either the store is not created yet, or the store is already at the latest model version. In either case, we already know that the store will use the latest version
|
||||
return migrations.first?.sourceVersion
|
||||
?? allVersions.last!
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
var currentPeriod: Advanced.EvolutionDemo.GeologicalPeriod = Advanced.EvolutionDemo.GeologicalPeriod.allCases.last! {
|
||||
|
||||
didSet {
|
||||
|
||||
self.selectModelVersion(self.currentPeriod)
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var current: (
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
dataStack: DataStack,
|
||||
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||
)? {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var isBusy: Bool = false
|
||||
|
||||
private(set) var progress: Progress?
|
||||
|
||||
|
||||
init() {
|
||||
|
||||
self.synchronizeCurrentVersion()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func synchronizeCurrentVersion() {
|
||||
|
||||
guard
|
||||
let currentPeriod = Advanced.EvolutionDemo.GeologicalPeriod(rawValue: self.findCurrentVersion())
|
||||
else {
|
||||
|
||||
self.selectModelVersion(self.currentPeriod)
|
||||
return
|
||||
}
|
||||
self.selectModelVersion(currentPeriod)
|
||||
}
|
||||
|
||||
private func selectModelVersion(_ period: Advanced.EvolutionDemo.GeologicalPeriod) {
|
||||
|
||||
let currentPeriod = self.current?.period
|
||||
guard period != currentPeriod else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.objectWillChange.send()
|
||||
|
||||
self.isBusy = true
|
||||
|
||||
// explicitly trigger `NSPersistentStore` cleanup by deallocating the `DataStack`
|
||||
self.current = nil
|
||||
|
||||
let migrationChain: MigrationChain
|
||||
switch (currentPeriod?.version, period.version) {
|
||||
|
||||
case (nil, let newVersion):
|
||||
migrationChain = [newVersion]
|
||||
|
||||
case (let currentVersion?, let newVersion):
|
||||
let upgradeMigrationChain = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
||||
.map({ $0.version })
|
||||
let currentVersionIndex = upgradeMigrationChain.firstIndex(of: currentVersion)!
|
||||
let newVersionIndex = upgradeMigrationChain.firstIndex(of: newVersion)!
|
||||
|
||||
migrationChain = MigrationChain(
|
||||
currentVersionIndex > newVersionIndex
|
||||
? upgradeMigrationChain.reversed()
|
||||
: upgradeMigrationChain
|
||||
)
|
||||
}
|
||||
let dataStack = self.createDataStack(
|
||||
exactCurrentModelVersion: period.version,
|
||||
migrationChain: migrationChain
|
||||
)
|
||||
|
||||
let completion = { [weak self] () -> Void in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.objectWillChange.send()
|
||||
defer {
|
||||
|
||||
self.isBusy = false
|
||||
}
|
||||
self.current = (
|
||||
period: period,
|
||||
dataStack: dataStack,
|
||||
dataSource: period.creatureType.dataSource(in: dataStack)
|
||||
)
|
||||
self.currentPeriod = period
|
||||
}
|
||||
|
||||
self.progress = dataStack.addStorage(
|
||||
self.accessSQLiteStore(),
|
||||
completion: { [weak self] result in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
guard case .success = result else {
|
||||
|
||||
self.objectWillChange.send()
|
||||
self.isBusy = false
|
||||
return
|
||||
}
|
||||
if self.progress == nil {
|
||||
|
||||
self.spawnCreatures(in: dataStack, period: period, completion: completion)
|
||||
}
|
||||
else {
|
||||
|
||||
completion()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func spawnCreatures(
|
||||
in dataStack: DataStack,
|
||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let creatureType = period.creatureType
|
||||
for dnaCode in try creatureType.count(in: transaction) ..< 10000 {
|
||||
|
||||
let object = creatureType.create(in: transaction)
|
||||
object.dnaCode = Int64(dnaCode)
|
||||
object.mutate(in: transaction)
|
||||
}
|
||||
},
|
||||
completion: { _ in completion() }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - VersionMetadata
|
||||
|
||||
private struct VersionMetadata {
|
||||
|
||||
let label: String
|
||||
let entityType: Advanced.EvolutionDemo.CreatureType.Type
|
||||
let schemaHistory: SchemaHistory
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Demo/⭐️Sources/⭐️Demos/⭐️Classic/Classic.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
// MARK: - Classic
|
||||
|
||||
/**
|
||||
Sample usages for `NSManagedObject` subclasses
|
||||
*/
|
||||
enum Classic {}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.DetailView
|
||||
|
||||
struct DetailView: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(_ palette: ObjectMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
self.palette = palette
|
||||
}
|
||||
|
||||
// MARK: UIViewControllerRepresentable
|
||||
|
||||
typealias UIViewControllerType = Classic.ColorsDemo.DetailViewController
|
||||
|
||||
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
|
||||
|
||||
return UIViewControllerType(self.palette)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {
|
||||
|
||||
uiViewController.palette = self.palette
|
||||
}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||
|
||||
func makeCoordinator() -> ObjectMonitor<Classic.ColorsDemo.Palette> {
|
||||
|
||||
return self.palette
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let palette: ObjectMonitor<Classic.ColorsDemo.Palette>
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Classic_ColorsDemo_DetailView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
try! Classic.ColorsDemo.dataStack.perform(
|
||||
synchronous: { transaction in
|
||||
|
||||
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
|
||||
return
|
||||
}
|
||||
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
||||
palette.setRandomHue()
|
||||
}
|
||||
)
|
||||
|
||||
return Classic.ColorsDemo.DetailView(
|
||||
Classic.ColorsDemo.dataStack.monitorObject(
|
||||
Classic.ColorsDemo.palettesMonitor[0, 0]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.Filter
|
||||
|
||||
enum Filter: String, CaseIterable {
|
||||
|
||||
case all = "All Colors"
|
||||
case light = "Light Colors"
|
||||
case dark = "Dark Colors"
|
||||
|
||||
func next() -> Filter {
|
||||
|
||||
let allCases = Self.allCases
|
||||
return allCases[(allCases.firstIndex(of: self)! + 1) % allCases.count]
|
||||
}
|
||||
|
||||
func whereClause() -> Where<Classic.ColorsDemo.Palette> {
|
||||
|
||||
switch self {
|
||||
|
||||
case .all: return .init()
|
||||
case .light: return (\.brightness >= 0.6)
|
||||
case .dark: return (\.brightness <= 0.4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.ItemCell
|
||||
|
||||
final class ItemCell: UITableViewCell {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let reuseIdentifier: String = NSStringFromClass(Classic.ColorsDemo.ItemCell.self)
|
||||
|
||||
func setPalette(_ palette: Classic.ColorsDemo.Palette) {
|
||||
|
||||
self.contentView.backgroundColor = palette.color
|
||||
self.textLabel?.text = palette.colorText
|
||||
self.textLabel?.textColor = palette.brightness > 0.6 ? .black : .white
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.ListView
|
||||
|
||||
struct ListView: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
listMonitor: ListMonitor<Classic.ColorsDemo.Palette>,
|
||||
onPaletteTapped: @escaping (Classic.ColorsDemo.Palette) -> Void
|
||||
) {
|
||||
|
||||
self.listMonitor = listMonitor
|
||||
self.onPaletteTapped = onPaletteTapped
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewControllerRepresentable
|
||||
|
||||
typealias UIViewControllerType = Classic.ColorsDemo.ListViewController
|
||||
|
||||
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
|
||||
|
||||
return UIViewControllerType(
|
||||
listMonitor: self.listMonitor,
|
||||
onPaletteTapped: self.onPaletteTapped
|
||||
)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {
|
||||
|
||||
uiViewController.setEditing(
|
||||
context.environment.editMode?.wrappedValue.isEditing == true,
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
|
||||
private let onPaletteTapped: (Classic.ColorsDemo.Palette) -> Void
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Classic_ColorsDemo_ListView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
let minimumSamples = 10
|
||||
try! Classic.ColorsDemo.dataStack.perform(
|
||||
synchronous: { transaction in
|
||||
|
||||
let missing = minimumSamples
|
||||
- (try transaction.fetchCount(From<Classic.ColorsDemo.Palette>()))
|
||||
guard missing > 0 else {
|
||||
return
|
||||
}
|
||||
for _ in 0..<missing {
|
||||
|
||||
let palette = transaction.create(Into<Classic.ColorsDemo.Palette>())
|
||||
palette.setRandomHue()
|
||||
}
|
||||
}
|
||||
)
|
||||
return Classic.ColorsDemo.ListView(
|
||||
listMonitor: Classic.ColorsDemo.palettesMonitor,
|
||||
onPaletteTapped: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.MainView
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init() {
|
||||
|
||||
let listMonitor = Classic.ColorsDemo.palettesMonitor
|
||||
self.listMonitor = listMonitor
|
||||
self.listHelper = .init(listMonitor: listMonitor)
|
||||
self._filter = Binding(
|
||||
get: { Classic.ColorsDemo.filter },
|
||||
set: { Classic.ColorsDemo.filter = $0 }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
let detailView: AnyView
|
||||
if let selectedObject = self.listHelper.selectedObject() {
|
||||
|
||||
detailView = AnyView(
|
||||
Classic.ColorsDemo.DetailView(selectedObject)
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
detailView = AnyView(EmptyView())
|
||||
}
|
||||
let listMonitor = self.listMonitor
|
||||
return VStack(spacing: 0) {
|
||||
Classic.ColorsDemo.ListView
|
||||
.init(
|
||||
listMonitor: listMonitor,
|
||||
onPaletteTapped: {
|
||||
|
||||
self.listHelper.setSelectedPalette($0)
|
||||
}
|
||||
)
|
||||
.navigationBarTitle(
|
||||
Text("Colors (\(self.listHelper.count) objects)")
|
||||
)
|
||||
.frame(minHeight: 0, maxHeight: .infinity)
|
||||
.edgesIgnoringSafeArea(.vertical)
|
||||
detailView
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.frame(minHeight: 0, maxHeight: .infinity)
|
||||
}
|
||||
.navigationBarItems(
|
||||
leading: HStack {
|
||||
EditButton()
|
||||
Button(
|
||||
action: { self.clearColors() },
|
||||
label: { Text("Clear") }
|
||||
)
|
||||
},
|
||||
trailing: HStack {
|
||||
Button(
|
||||
action: { self.changeFilter() },
|
||||
label: { Text(self.filter.rawValue) }
|
||||
)
|
||||
Button(
|
||||
action: { self.shuffleColors() },
|
||||
label: { Text("Shuffle") }
|
||||
)
|
||||
Button(
|
||||
action: { self.addColor() },
|
||||
label: { Text("Add") }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
|
||||
|
||||
@ObservedObject
|
||||
private var listHelper: ListHelper
|
||||
|
||||
@Binding
|
||||
private var filter: Classic.ColorsDemo.Filter
|
||||
|
||||
private func changeFilter() {
|
||||
|
||||
Classic.ColorsDemo.filter = Classic.ColorsDemo.filter.next()
|
||||
}
|
||||
|
||||
private func clearColors() {
|
||||
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
try transaction.deleteAll(From<Classic.ColorsDemo.Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
private func addColor() {
|
||||
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
_ = transaction.create(Into<Classic.ColorsDemo.Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
private func shuffleColors() {
|
||||
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { transaction in
|
||||
|
||||
for palette in try transaction.fetchAll(From<Classic.ColorsDemo.Palette>()) {
|
||||
|
||||
palette.setRandomHue()
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Classic.ColorsDemo.MainView.ListHelper
|
||||
|
||||
fileprivate final class ListHelper: ObservableObject, ListObjectObserver {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate private(set) var count: Int = 0
|
||||
|
||||
fileprivate init(listMonitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
listMonitor.addObserver(self)
|
||||
self.count = listMonitor.numberOfObjects()
|
||||
}
|
||||
|
||||
fileprivate func selectedObject() -> ObjectMonitor<Classic.ColorsDemo.Palette>? {
|
||||
|
||||
return self.selectedPalette.flatMap {
|
||||
|
||||
guard !$0.isDeleted else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return Classic.ColorsDemo.dataStack.monitorObject($0)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setSelectedPalette(_ palette: Classic.ColorsDemo.Palette?) {
|
||||
|
||||
guard self.selectedPalette != palette else {
|
||||
|
||||
return
|
||||
}
|
||||
self.objectWillChange.send()
|
||||
if let palette = palette, !palette.isDeleted {
|
||||
|
||||
self.selectedPalette = palette
|
||||
}
|
||||
else {
|
||||
|
||||
self.selectedPalette = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
typealias ListEntityType = Classic.ColorsDemo.Palette
|
||||
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
self.objectWillChange.send()
|
||||
self.count = monitor.numberOfObjects()
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<ListEntityType>) {
|
||||
|
||||
self.objectWillChange.send()
|
||||
self.count = monitor.numberOfObjects()
|
||||
}
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>, didDeleteObject object: Classic.ColorsDemo.Palette, fromIndexPath indexPath: IndexPath) {
|
||||
|
||||
if self.selectedPalette == object {
|
||||
|
||||
self.setSelectedPalette(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var selectedPalette: Classic.ColorsDemo.Palette?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Classic_ColorsDemo_MainView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
let minimumSamples = 10
|
||||
try! Classic.ColorsDemo.dataStack.perform(
|
||||
synchronous: { transaction in
|
||||
|
||||
let missing = minimumSamples
|
||||
- (try transaction.fetchCount(From<Classic.ColorsDemo.Palette>()))
|
||||
guard missing > 0 else {
|
||||
return
|
||||
}
|
||||
for _ in 0..<missing {
|
||||
|
||||
let palette = transaction.create(Into<Classic.ColorsDemo.Palette>())
|
||||
palette.setRandomHue()
|
||||
}
|
||||
}
|
||||
)
|
||||
return Classic.ColorsDemo.MainView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreData
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Classic.ColorsDemo.Palette
|
||||
|
||||
@objc(Classic_ColorsDemo_Palette)
|
||||
final class Classic_ColorsDemo_Palette: NSManagedObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@NSManaged
|
||||
dynamic var hue: Float
|
||||
|
||||
@NSManaged
|
||||
dynamic var saturation: Float
|
||||
|
||||
@NSManaged
|
||||
dynamic var brightness: Float
|
||||
|
||||
@objc
|
||||
dynamic var colorGroup: String! {
|
||||
|
||||
let key = #keyPath(colorGroup)
|
||||
if case let value as String = self.getValue(forKvcKey: key) {
|
||||
|
||||
return value
|
||||
}
|
||||
let newValue: String
|
||||
switch self.hue * 359 {
|
||||
|
||||
case 0 ..< 20: newValue = "Lower Reds"
|
||||
case 20 ..< 57: newValue = "Oranges and Browns"
|
||||
case 57 ..< 90: newValue = "Yellow-Greens"
|
||||
case 90 ..< 159: newValue = "Greens"
|
||||
case 159 ..< 197: newValue = "Blue-Greens"
|
||||
case 197 ..< 241: newValue = "Blues"
|
||||
case 241 ..< 297: newValue = "Violets"
|
||||
case 297 ..< 331: newValue = "Magentas"
|
||||
default: newValue = "Upper Reds"
|
||||
}
|
||||
self.setPrimitiveValue(newValue, forKey: key)
|
||||
return newValue
|
||||
}
|
||||
|
||||
var color: UIColor {
|
||||
|
||||
let newValue = UIColor(
|
||||
hue: CGFloat(self.hue),
|
||||
saturation: CGFloat(self.saturation),
|
||||
brightness: CGFloat(self.brightness),
|
||||
alpha: 1.0
|
||||
)
|
||||
return newValue
|
||||
}
|
||||
|
||||
var colorText: String {
|
||||
|
||||
let newValue: String = "H: \(self.hue * 359)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%"
|
||||
return newValue
|
||||
}
|
||||
|
||||
func setRandomHue() {
|
||||
|
||||
self.hue = Self.randomHue()
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSManagedObject
|
||||
|
||||
public override func awakeFromInsert() {
|
||||
|
||||
super.awakeFromInsert()
|
||||
|
||||
self.hue = Self.randomHue()
|
||||
self.saturation = Self.randomSaturation()
|
||||
self.brightness = Self.randomBrightness()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static func randomHue() -> Float {
|
||||
|
||||
return Float.random(in: 0.0 ... 1.0)
|
||||
}
|
||||
|
||||
private static func randomSaturation() -> Float {
|
||||
|
||||
return Float.random(in: 0.4 ... 1.0)
|
||||
}
|
||||
|
||||
private static func randomBrightness() -> Float {
|
||||
|
||||
return Float.random(in: 0.1 ... 0.9)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Classic
|
||||
|
||||
extension Classic {
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
/**
|
||||
Sample usages for observing lists or single instances of `NSManagedObject`s
|
||||
*/
|
||||
enum ColorsDemo {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
typealias Palette = Classic_ColorsDemo_Palette
|
||||
|
||||
static let dataStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(
|
||||
xcodeModelName: "Classic.ColorsDemo",
|
||||
bundle: Bundle(for: Palette.self)
|
||||
)
|
||||
|
||||
/**
|
||||
- Important: `addStorageAndWait(_:)` was used here to simplify initializing the demo, but in practice the asynchronous function variants are recommended.
|
||||
*/
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "Classic.ColorsDemo.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
return dataStack
|
||||
}()
|
||||
|
||||
static let palettesMonitor: ListMonitor<Classic.ColorsDemo.Palette> = Classic.ColorsDemo.dataStack.monitorSectionedList(
|
||||
From<Classic.ColorsDemo.Palette>()
|
||||
.sectionBy(\.colorGroup)
|
||||
.where(Classic.ColorsDemo.filter.whereClause())
|
||||
.orderBy(.ascending(\.hue))
|
||||
)
|
||||
|
||||
static var filter: Classic.ColorsDemo.Filter = .all {
|
||||
|
||||
didSet {
|
||||
|
||||
Classic.ColorsDemo.palettesMonitor.refetch(
|
||||
self.filter.whereClause(),
|
||||
OrderBy<Classic.ColorsDemo.Palette>(.ascending(\.hue))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Palette" representedClassName="Classic_ColorsDemo_Palette" syncable="YES">
|
||||
<attribute name="brightness" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="colorGroup" optional="YES" transient="YES" attributeType="String"/>
|
||||
<attribute name="hue" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="saturation" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Palette" positionX="-63" positionY="-18" width="128" height="103"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.DetailViewController
|
||||
|
||||
final class DetailViewController: UIViewController, ObjectObserver {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: We can normally use `ObjectPublisher` directly, which is simpler. But for this demo, we will be using `ObjectMonitor` instead because we need to keep track of which properties change to prevent our `UISlider` from stuttering. Refer to the `objectMonitor(_:didUpdateObject:changedPersistentKeys:)` implementation below.
|
||||
*/
|
||||
var palette: ObjectMonitor<Classic.ColorsDemo.Palette> {
|
||||
|
||||
didSet {
|
||||
|
||||
oldValue.removeObserver(self)
|
||||
|
||||
self.startMonitoringObject()
|
||||
}
|
||||
}
|
||||
|
||||
init(_ palette: ObjectMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
self.palette = palette
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Once the views are created, we can start receiving `ObjectMonitor` updates in our `ObjectObserver` conformance methods. We typically call this at the end of `viewDidLoad`. Note that after the `addObserver` call, only succeeding updates will trigger our `ObjectObserver` methods, so to immediately display the current values, we need to initialize our views once (in this case, using `reloadPaletteInfo(_:changedKeys:)`.
|
||||
*/
|
||||
private func startMonitoringObject() {
|
||||
|
||||
self.palette.addObserver(self)
|
||||
if let palette = self.palette.object {
|
||||
|
||||
self.reloadPaletteInfo(palette, changedKeys: nil)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ObjectMonitor`s safely remove deallocated observers automatically.
|
||||
*/
|
||||
deinit {
|
||||
|
||||
self.palette.removeObserver(self)
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 4: Our `objectMonitor(_:didUpdateObject:changedPersistentKeys:)` implementation passes a `Set<KeyPathString>` to our reload method. We can then inspect which values were triggered by each `UISlider`, so we can avoid double-updates that can lag the `UISlider` dragging.
|
||||
*/
|
||||
func reloadPaletteInfo(
|
||||
_ palette: Classic.ColorsDemo.Palette,
|
||||
changedKeys: Set<KeyPathString>?
|
||||
) {
|
||||
|
||||
self.view.backgroundColor = palette.color
|
||||
|
||||
self.hueLabel.text = "H: \(Int(palette.hue * 359))°"
|
||||
self.saturationLabel.text = "S: \(Int(palette.saturation * 100))%"
|
||||
self.brightnessLabel.text = "B: \(Int(palette.brightness * 100))%"
|
||||
|
||||
if changedKeys == nil
|
||||
|| changedKeys?.contains(String(keyPath: \Classic.ColorsDemo.Palette.hue)) == true {
|
||||
|
||||
self.hueSlider.value = Float(palette.hue)
|
||||
}
|
||||
if changedKeys == nil
|
||||
|| changedKeys?.contains(String(keyPath: \Classic.ColorsDemo.Palette.saturation)) == true {
|
||||
|
||||
self.saturationSlider.value = palette.saturation
|
||||
}
|
||||
if changedKeys == nil
|
||||
|| changedKeys?.contains(String(keyPath: \Classic.ColorsDemo.Palette.brightness)) == true {
|
||||
|
||||
self.brightnessSlider.value = palette.brightness
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: ObjectObserver
|
||||
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<Classic.ColorsDemo.Palette>,
|
||||
didUpdateObject object: Classic.ColorsDemo.Palette,
|
||||
changedPersistentKeys: Set<KeyPathString>
|
||||
) {
|
||||
|
||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
let view = self.view!
|
||||
let containerView = UIView()
|
||||
do {
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.backgroundColor = UIColor.white
|
||||
containerView.layer.cornerRadius = 10
|
||||
containerView.layer.masksToBounds = true
|
||||
containerView.layer.shadowColor = UIColor(white: 0.5, alpha: 0.3).cgColor
|
||||
containerView.layer.shadowOffset = .init(width: 1, height: 1)
|
||||
containerView.layer.shadowRadius = 2
|
||||
|
||||
view.addSubview(containerView)
|
||||
}
|
||||
|
||||
let vStackView = UIStackView()
|
||||
do {
|
||||
vStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
vStackView.axis = .vertical
|
||||
vStackView.spacing = 10
|
||||
vStackView.distribution = .fill
|
||||
vStackView.alignment = .fill
|
||||
|
||||
containerView.addSubview(vStackView)
|
||||
}
|
||||
|
||||
let palette = self.palette.object
|
||||
let rows: [(label: UILabel, slider: UISlider, initialValue: Float, sliderValueChangedSelector: Selector)] = [
|
||||
(
|
||||
self.hueLabel,
|
||||
self.hueSlider,
|
||||
palette?.hue ?? 0,
|
||||
#selector(self.hueSliderValueDidChange(_:))
|
||||
),
|
||||
(
|
||||
self.saturationLabel,
|
||||
self.saturationSlider,
|
||||
palette?.saturation ?? 0,
|
||||
#selector(self.saturationSliderValueDidChange(_:))
|
||||
),
|
||||
(
|
||||
self.brightnessLabel,
|
||||
self.brightnessSlider,
|
||||
palette?.brightness ?? 0,
|
||||
#selector(self.brightnessSliderValueDidChange(_:))
|
||||
)
|
||||
]
|
||||
for (label, slider, initialValue, sliderValueChangedSelector) in rows {
|
||||
|
||||
let hStackView = UIStackView()
|
||||
do {
|
||||
hStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
hStackView.axis = .horizontal
|
||||
hStackView.spacing = 5
|
||||
hStackView.distribution = .fill
|
||||
hStackView.alignment = .center
|
||||
|
||||
vStackView.addArrangedSubview(hStackView)
|
||||
}
|
||||
do {
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = UIColor(white: 0, alpha: 0.8)
|
||||
label.textAlignment = .center
|
||||
|
||||
hStackView.addArrangedSubview(label)
|
||||
}
|
||||
do {
|
||||
slider.translatesAutoresizingMaskIntoConstraints = false
|
||||
slider.minimumValue = 0
|
||||
slider.maximumValue = 1
|
||||
slider.value = initialValue
|
||||
slider.addTarget(
|
||||
self,
|
||||
action: sliderValueChangedSelector,
|
||||
for: .valueChanged
|
||||
)
|
||||
|
||||
hStackView.addArrangedSubview(slider)
|
||||
}
|
||||
}
|
||||
|
||||
layout: do {
|
||||
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
containerView.leadingAnchor.constraint(
|
||||
equalTo: view.safeAreaLayoutGuide.leadingAnchor,
|
||||
constant: 10
|
||||
),
|
||||
containerView.bottomAnchor.constraint(
|
||||
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
|
||||
constant: -10
|
||||
),
|
||||
containerView.trailingAnchor.constraint(
|
||||
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
|
||||
constant: -10
|
||||
),
|
||||
|
||||
vStackView.topAnchor.constraint(
|
||||
equalTo: containerView.topAnchor,
|
||||
constant: 15
|
||||
),
|
||||
vStackView.leadingAnchor.constraint(
|
||||
equalTo: containerView.leadingAnchor,
|
||||
constant: 15
|
||||
),
|
||||
vStackView.bottomAnchor.constraint(
|
||||
equalTo: containerView.bottomAnchor,
|
||||
constant: -15
|
||||
),
|
||||
vStackView.trailingAnchor.constraint(
|
||||
equalTo: containerView.trailingAnchor,
|
||||
constant: -15
|
||||
)
|
||||
]
|
||||
)
|
||||
NSLayoutConstraint.activate(
|
||||
rows.map { label, _, _, _ in
|
||||
label.widthAnchor.constraint(equalToConstant: 80)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
self.startMonitoringObject()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
|
||||
private let hueLabel: UILabel = .init()
|
||||
private let saturationLabel: UILabel = .init()
|
||||
private let brightnessLabel: UILabel = .init()
|
||||
private let hueSlider: UISlider = .init()
|
||||
private let saturationSlider: UISlider = .init()
|
||||
private let brightnessSlider: UISlider = .init()
|
||||
|
||||
@objc
|
||||
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
||||
|
||||
let value = sender.value
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
let palette = transaction.edit(self?.palette.object)
|
||||
palette?.hue = value
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@objc
|
||||
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
||||
|
||||
let value = sender.value
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
let palette = transaction.edit(self?.palette.object)
|
||||
palette?.saturation = value
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@objc
|
||||
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
||||
|
||||
let value = sender.value
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
let palette = transaction.edit(self?.palette.object)
|
||||
palette?.brightness = value
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Classic.ColorsDemo
|
||||
|
||||
extension Classic.ColorsDemo {
|
||||
|
||||
// MARK: - Classic.ColorsDemo.ListViewController
|
||||
|
||||
final class ListViewController: UITableViewController, ListSectionObserver {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Once the views are created, we can start observing `ListMonitor` updates. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `tableView.reloadData()` once.
|
||||
*/
|
||||
private func startObservingList() {
|
||||
|
||||
self.listMonitor.addObserver(self)
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ListMonitor`s safely remove deallocated observers automatically.
|
||||
*/
|
||||
deinit {
|
||||
|
||||
self.listMonitor.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: `ListSectionObserver` (and inherently, `ListObjectObserver` and `ListObserver`) conformance
|
||||
*/
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
typealias ListEntityType = Classic.ColorsDemo.Palette
|
||||
|
||||
func listMonitorWillChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
||||
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) {
|
||||
|
||||
self.tableView.insertRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) {
|
||||
|
||||
self.tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) {
|
||||
|
||||
if case let cell as Classic.ColorsDemo.ItemCell = self.tableView.cellForRow(at: indexPath) {
|
||||
|
||||
cell.setPalette(object)
|
||||
}
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
|
||||
|
||||
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
|
||||
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListSectionObserver
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return self.listMonitor.numberOfSections()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.listMonitor.numberOfObjects(in: section)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: Classic.ColorsDemo.ItemCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! Classic.ColorsDemo.ItemCell
|
||||
cell.setPalette(self.listMonitor[indexPath])
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return self.listMonitor.sectionInfo(at: section).name
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
|
||||
switch editingStyle {
|
||||
|
||||
case .delete:
|
||||
let object = self.listMonitor[indexPath]
|
||||
Classic.ColorsDemo.dataStack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
transaction.delete(object)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
listMonitor: ListMonitor<Classic.ColorsDemo.Palette>,
|
||||
onPaletteTapped: @escaping (Classic.ColorsDemo.Palette) -> Void
|
||||
) {
|
||||
|
||||
self.listMonitor = listMonitor
|
||||
self.onPaletteTapped = onPaletteTapped
|
||||
|
||||
super.init(style: .plain)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.register(
|
||||
Classic.ColorsDemo.ItemCell.self,
|
||||
forCellReuseIdentifier: Classic.ColorsDemo.ItemCell.reuseIdentifier
|
||||
)
|
||||
|
||||
self.startObservingList()
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
self.onPaletteTapped(
|
||||
self.listMonitor[indexPath]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
|
||||
private let onPaletteTapped: (Classic.ColorsDemo.Palette) -> Void
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Demo/⭐️Sources/⭐️Demos/⭐️Modern/Modern.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
// MARK: - Modern
|
||||
|
||||
/**
|
||||
Sample usages for `CoreStoreObject` subclasses
|
||||
*/
|
||||
enum Modern {}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Modern.ColorsDemo
|
||||
|
||||
extension Modern.ColorsDemo {
|
||||
|
||||
// MARK: - Modern.ColorsDemo.Filter
|
||||
|
||||
enum Filter: String, CaseIterable {
|
||||
|
||||
case all = "All Colors"
|
||||
case light = "Light Colors"
|
||||
case dark = "Dark Colors"
|
||||
|
||||
func next() -> Filter {
|
||||
|
||||
let allCases = Self.allCases
|
||||
return allCases[(allCases.firstIndex(of: self)! + 1) % allCases.count]
|
||||
}
|
||||
|
||||
func whereClause() -> Where<Modern.ColorsDemo.Palette> {
|
||||
|
||||
switch self {
|
||||
|
||||
case .all: return .init()
|
||||
case .light: return (\.$brightness >= 0.6)
|
||||
case .dark: return (\.$brightness <= 0.4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Modern.ColorsDemo
|
||||
|
||||
extension Modern.ColorsDemo {
|
||||
|
||||
// MARK: - Modern.ColorsDemo.Palette
|
||||
|
||||
final class Palette: CoreStoreObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored(
|
||||
"hue",
|
||||
customSetter: { object, field, value in
|
||||
|
||||
Palette.resetVirtualProperties(object)
|
||||
field.primitiveValue = value
|
||||
},
|
||||
dynamicInitialValue: { Palette.randomHue() }
|
||||
)
|
||||
var hue: Float
|
||||
|
||||
@Field.Stored(
|
||||
"saturation",
|
||||
customSetter: { object, field, value in
|
||||
|
||||
Palette.resetVirtualProperties(object)
|
||||
field.primitiveValue = value
|
||||
},
|
||||
dynamicInitialValue: { Palette.randomSaturation() }
|
||||
)
|
||||
var saturation: Float
|
||||
|
||||
@Field.Stored(
|
||||
"brightness",
|
||||
customSetter: { object, field, value in
|
||||
|
||||
Palette.resetVirtualProperties(object)
|
||||
field.primitiveValue = value
|
||||
},
|
||||
dynamicInitialValue: { Palette.randomBrightness() }
|
||||
)
|
||||
var brightness: Float
|
||||
|
||||
@Field.Virtual(
|
||||
"colorGroup",
|
||||
customGetter: { object, field in
|
||||
|
||||
if let colorGroup = field.primitiveValue {
|
||||
|
||||
return colorGroup
|
||||
}
|
||||
let colorGroup: String
|
||||
switch object.$hue.value * 359 {
|
||||
|
||||
case 0 ..< 20: colorGroup = "Lower Reds"
|
||||
case 20 ..< 57: colorGroup = "Oranges and Browns"
|
||||
case 57 ..< 90: colorGroup = "Yellow-Greens"
|
||||
case 90 ..< 159: colorGroup = "Greens"
|
||||
case 159 ..< 197: colorGroup = "Blue-Greens"
|
||||
case 197 ..< 241: colorGroup = "Blues"
|
||||
case 241 ..< 297: colorGroup = "Violets"
|
||||
case 297 ..< 331: colorGroup = "Magentas"
|
||||
default: colorGroup = "Upper Reds"
|
||||
}
|
||||
field.primitiveValue = colorGroup
|
||||
return colorGroup
|
||||
}
|
||||
)
|
||||
var colorGroup: String
|
||||
|
||||
@Field.Virtual(
|
||||
"color",
|
||||
customGetter: { object, field in
|
||||
|
||||
if let color = field.primitiveValue {
|
||||
|
||||
return color
|
||||
}
|
||||
let color = UIColor(
|
||||
hue: CGFloat(object.$hue.value),
|
||||
saturation: CGFloat(object.$saturation.value),
|
||||
brightness: CGFloat(object.$brightness.value),
|
||||
alpha: 1.0
|
||||
)
|
||||
field.primitiveValue = color
|
||||
return color
|
||||
}
|
||||
)
|
||||
var color: UIColor
|
||||
|
||||
@Field.Virtual(
|
||||
"colorText",
|
||||
customGetter: { object, field in
|
||||
|
||||
if let colorText = field.primitiveValue {
|
||||
|
||||
return colorText
|
||||
}
|
||||
let colorText = "H: \(object.$hue.value * 359)˚, S: \(round(object.$saturation.value * 100.0))%, B: \(round(object.$brightness.value * 100.0))%"
|
||||
field.primitiveValue = colorText
|
||||
return colorText
|
||||
}
|
||||
)
|
||||
var colorText: String
|
||||
|
||||
func setRandomHue() {
|
||||
|
||||
self.hue = Self.randomHue()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static func resetVirtualProperties(_ object: ObjectProxy<Modern.ColorsDemo.Palette>) {
|
||||
|
||||
object.$colorGroup.primitiveValue = nil
|
||||
object.$color.primitiveValue = nil
|
||||
object.$colorText.primitiveValue = nil
|
||||
}
|
||||
|
||||
private static func randomHue() -> Float {
|
||||
|
||||
return Float.random(in: 0.0 ... 1.0)
|
||||
}
|
||||
|
||||
private static func randomSaturation() -> Float {
|
||||
|
||||
return Float.random(in: 0.4 ... 1.0)
|
||||
}
|
||||
|
||||
private static func randomBrightness() -> Float {
|
||||
|
||||
return Float.random(in: 0.1 ... 0.9)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
|
||||
// MARK: - Modern.ColorsDemo
|
||||
|
||||
extension Modern.ColorsDemo {
|
||||
|
||||
// MARK: - SwiftUI
|
||||
|
||||
enum SwiftUI {}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Modern.ColorsDemo.UIKit
|
||||
|
||||
extension Modern.ColorsDemo.UIKit {
|
||||
|
||||
// MARK: - Modern.ColorsDemo.UIKit.DetailView
|
||||
|
||||
struct DetailView: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
|
||||
|
||||
self.palette = palette
|
||||
}
|
||||
|
||||
// MARK: UIViewControllerRepresentable
|
||||
|
||||
typealias UIViewControllerType = Modern.ColorsDemo.UIKit.DetailViewController
|
||||
|
||||
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
|
||||
|
||||
return UIViewControllerType(self.palette)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let palette: ObjectPublisher<Modern.ColorsDemo.Palette>
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Modern_ColorsDemo_UIKit_DetailView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
try! Modern.ColorsDemo.dataStack.perform(
|
||||
synchronous: { transaction in
|
||||
|
||||
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
|
||||
return
|
||||
}
|
||||
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
||||
palette.setRandomHue()
|
||||
}
|
||||
)
|
||||
|
||||
return Modern.ColorsDemo.UIKit.DetailView(
|
||||
Modern.ColorsDemo.palettesPublisher.snapshot.first!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||