This commit is contained in:
John Estropia
2020-08-18 12:05:20 +09:00
parent 72f36e7237
commit 0f3455a4a4
13 changed files with 557 additions and 104 deletions

View File

@@ -7,6 +7,10 @@
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 */; };
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 */; };
@@ -81,6 +85,10 @@
/* 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>"; };
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>"; };
@@ -217,6 +225,7 @@
B5A3913324E6170500E7E8BD /* Menu.swift */,
B5A3912024E5429200E7E8BD /* Menu.MainView.swift */,
B5A3915224E6537F00E7E8BD /* Menu.ItemView.swift */,
B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */,
);
path = Menu;
sourceTree = "<group>";
@@ -364,6 +373,8 @@
isa = PBXGroup;
children = (
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */,
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */,
B5A391B224E96B7400E7E8BD /* Models */,
);
path = PokedexDemo;
@@ -372,6 +383,7 @@
B5A391B224E96B7400E7E8BD /* Models */ = {
isa = PBXGroup;
children = (
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */,
B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift */,
B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */,
B5A391B524E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift */,
@@ -460,46 +472,50 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5A3918824E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift in Sources */,
B5A391AE24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */,
B5A391A224E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */,
B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */,
B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */,
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */,
B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */,
B5A391A824E90F1000E7E8BD /* UIImage+Extensions.swift in Sources */,
B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */,
B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */,
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */,
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */,
B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */,
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */,
B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */,
B5A3919624E7E4AC00E7E8BD /* Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */,
B5A3919A24E8207A00E7E8BD /* Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */,
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */,
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */,
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */,
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */,
B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */,
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */,
B5A391B624E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift in Sources */,
B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */,
B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */,
B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */,
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */,
B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */,
B5A3919424E7E36700E7E8BD /* Modern.ColorsDemo.SwiftUI.ListView.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 */,
B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */,
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */,
B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */,
B5A3918F24E7E06500E7E8BD /* Modern.ColorsDemo.swift in Sources */,
B5A3915E24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift in Sources */,
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift in Sources */,
B5A391A824E90F1000E7E8BD /* UIImage+Extensions.swift in Sources */,
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */,
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */,
B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */,
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */,
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */,
B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */,
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */,
B5A391A624E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.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 */,
B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */,
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */,
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */,
B5A391BD24E977E500E7E8BD /* Modern.PokedexDemo.Ability.swift in Sources */,
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */,
B5A391B624E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift in Sources */,
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */,
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */,
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift in Sources */,
B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */,
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */,
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */,
B5A3918824E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift in Sources */,
B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */,
B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.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 */,
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */,
B5A391AE24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */,
B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */,
B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */,
B5A391A224E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -533,7 +549,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = appIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -607,7 +623,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = appIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";

View File

@@ -3,7 +3,6 @@
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -19,20 +18,24 @@
<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"/>
<color key="textColor" name="foreground"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="coreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="IrK-8p-pit">
<rect key="frame" x="37" y="143.5" width="340" height="340"/>
<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="503.5" width="374" height="57.5"/>
<rect key="frame" x="20" y="418.5" width="374" height="57.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<color key="textColor" name="foreground"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" name="background"/>
<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"/>
@@ -52,12 +55,6 @@
</scene>
</scenes>
<resources>
<image name="coreStoreIcon" width="340" height="340"/>
<namedColor name="background">
<color red="0.15700000524520874" green="0.2199999988079071" blue="0.29399999976158142" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="foreground">
<color red="0.90600001811981201" green="0.92500001192092896" blue="0.92900002002716064" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<image name="CoreStoreIcon" width="170" height="170"/>
</resources>
</document>

View File

@@ -132,7 +132,7 @@ extension Modern.ColorsDemo {
private static func randomSaturation() -> Float {
return Float.random(in: 0.0 ... 1.0)
return Float.random(in: 0.4 ... 1.0)
}
private static func randomBrightness() -> Float {

View File

@@ -0,0 +1,74 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Combine
import CoreStore
import SwiftUI
// MARK: - Modern.PokedexDemo
extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.MainView
struct MainView: View {
/**
Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
*/
@ObservedObject
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
// MARK: Internal
init() {
self.pokedexEntries = Modern.PokedexDemo.pokedexEntries
}
// MARK: View
var body: some View {
List() {
ForEach(self.pokedexEntries.snapshot, id: \.self) { pokedexEntry in
LazyView {
Text(pokedexEntry.snapshot?.$id ?? "")
}
}
}
.overlay(
InstructionsView(
("Random", "Sets random coordinate"),
("Tap", "Sets to tapped coordinate")
)
.padding(.leading, 10)
.padding(.bottom, 40),
alignment: .bottomLeading
)
.navigationBarTitle("Pokedex")
}
// MARK: Private
private let service: Modern.PokedexDemo.Service = .init()
}
}
#if DEBUG
struct _Demo_Modern_PokedexDemo_MainView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
Modern.PokedexDemo.MainView()
}
}
#endif

View File

@@ -0,0 +1,59 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Modern.PokedexDemo
extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.PokedexEntry
final class PokedexEntry: CoreStoreObject, ImportableUniqueObject {
// MARK: Internal
@Field.Stored("id")
var id: String = ""
@Field.Stored("url")
var url: URL!
@Field.Relationship("form")
var form: Modern.PokedexDemo.PokemonForm?
// MARK: ImportableObject
typealias ImportSource = Dictionary<String, Any>
// MARK: ImportableUniqueObject
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokedexEntry.$id)
var uniqueIDValue: String {
get {
return self.id
}
set {
self.id = newValue
}
}
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> String? {
return try Modern.PokedexDemo.Service.parseJSON(source["name"])
}
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
self.url = URL(string: try Modern.PokedexDemo.Service.parseJSON(source["url"]))
}
}
}

View File

@@ -19,15 +19,18 @@ extension Modern.PokedexDemo {
@Field.Stored("name")
var name: String = ""
@Field.Stored("weight")
var weight: Int = 0
@Field.Stored("pokemonType1")
var pokemonType1: Modern.PokedexDemo.PokemonType = .normal
@Field.Stored("pokemonType2")
var pokemonType2: Modern.PokedexDemo.PokemonType?
@Field.Relationship("species")
var species: Modern.PokedexDemo.PokemonSpecies?
@Field.Stored("spriteURL")
var spriteURL: URL?
@Field.Stored("statHitPoints")
@@ -49,23 +52,17 @@ extension Modern.PokedexDemo {
var statSpeed: Int = 0
@Field.Stored("spriteFrontURL")
var spriteFrontURL: URL?
@Field.Stored("spriteBackURL")
var spriteBackURL: URL?
@Field.Stored("spriteShinyFrontURL")
var spriteShinyFrontURL: URL?
@Field.Stored("spriteShinyBackURL")
var spriteShinyBackURL: URL?
@Field.Relationship("abilities", inverse: \.$learners)
var abilities: Set<Modern.PokedexDemo.Ability>
@Field.Relationship("moves", inverse: \.$learners)
var moves: Set<Modern.PokedexDemo.Move>
@Field.Relationship("pokedexEntry", inverse: \.$form)
var pokedexEntry: Modern.PokedexDemo.PokedexEntry?
@Field.Relationship("species", inverse: \.$forms)
var species: Modern.PokedexDemo.PokemonSpecies?
}
}

View File

@@ -22,8 +22,9 @@ extension Modern.PokedexDemo {
@Field.Stored("weight")
var weight: Int = 0
@Field.Relationship("forms", inverse: \.$species)
@Field.Relationship("forms")
var forms: Set<Modern.PokedexDemo.PokemonForm>
}
}

View File

@@ -0,0 +1,169 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Foundation
import Combine
import CoreStore
// MARK: - Modern.PokedexDemo
extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.Service
final class Service {
// MARK: Internal
@Published
var isLoading: Bool = true
@Published
var lastError: (error: Modern.PokedexDemo.Service.Error, retry: () -> Void)?
init() {
self.fetchPokedexEntries()
}
static func parseJSON<Output>(_ json: Any?) throws -> Output {
switch json {
case let json as Output:
return json
case let any:
throw Modern.PokedexDemo.Service.Error.parseError(
expected: Output.self,
actual: type(of: any)
)
}
}
func fetchPokedexEntries() {
self.cancellable["pokedexEntries"] = self.pokedexEntries
.handleEvents(
receiveSubscription: { [weak self] _ in
print("Fetching Pokedex Entries")
guard let self = self else {
return
}
self.lastError = nil
self.isLoading = true
}
)
.sink(
receiveCompletion: { [weak self] completion in
print("Result (Fetching Pokedex Entries): \(completion)")
guard let self = self else {
return
}
self.isLoading = false
switch completion {
case .finished:
self.lastError = nil
case .failure(let error):
self.lastError = (
error: error,
retry: { [weak self] in
self?.fetchPokedexEntries()
}
)
}
},
receiveValue: {}
)
}
func fetchPokemonForm(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
self.cancellable["pokedexEntry.\(pokedexEntry.$id)"] = URLSession.shared
.dataTaskPublisher(for: pokedexEntry.$url!)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in
}
)
}
// MARK: Private
private var cancellable: Dictionary<String, AnyCancellable> = [:]
private lazy var pokedexEntries: AnyPublisher<Void, Modern.PokedexDemo.Service.Error> = URLSession.shared
.dataTaskPublisher(
for: URL(string: "https://pokeapi.co/api/v2/pokemon?limit=10000&offset=0")!
)
.mapError({ .networkError($0) })
.flatMap(
{ output in
return Future<Void, Modern.PokedexDemo.Service.Error> { promise in
do {
let json: Dictionary<String, Any> = try Self.parseJSON(
try JSONSerialization.jsonObject(with: output.data, options: [])
)
let results: [Dictionary<String, Any>] = try Self.parseJSON(
json["results"]
)
Modern.PokedexDemo.dataStack.perform(
asynchronous: { transaction -> Void in
_ = try transaction.importUniqueObjects(
Into<Modern.PokedexDemo.PokedexEntry>(),
sourceArray: results
)
},
success: { result in
promise(.success(result))
},
failure: { error in
promise(.failure(.saveError(error)))
}
)
}
catch let error as Modern.PokedexDemo.Service.Error {
promise(.failure(error))
}
catch {
promise(.failure(.otherError(error)))
}
}
}
)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
// MARK: - Modern.PokedexDemo.Service.Error
enum Error: Swift.Error {
case networkError(URLError)
case parseError(expected: Any.Type, actual: Any.Type)
case saveError(CoreStoreError)
case otherError(Swift.Error)
}
}
}

View File

@@ -23,10 +23,11 @@ extension Modern {
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Modern.ColorsDemo.Palette>("Palette")
],
versionLock: [
"Palette": [0xbaf4eaee9353176a, 0xdd6ca918cc2b0c38, 0xd04fad8882d7cc34, 0x3e90ca38c091503f]
Entity<Modern.PokedexDemo.PokedexEntry>("PokedexEntry"),
Entity<Modern.PokedexDemo.PokemonSpecies>("PokemonSpecies"),
Entity<Modern.PokedexDemo.PokemonForm>("PokemonForm"),
Entity<Modern.PokedexDemo.Move>("Move"),
Entity<Modern.PokedexDemo.Ability>("Ability")
]
)
)
@@ -36,31 +37,16 @@ extension Modern {
*/
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "Modern.ColorsDemo.sqlite",
fileName: "Modern.PokedexDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return dataStack
}()
static let palettesPublisher: ListPublisher<Modern.ColorsDemo.Palette> = Modern.ColorsDemo.dataStack.publishList(
From<Modern.ColorsDemo.Palette>()
.sectionBy(\.$colorName)
.where(Modern.ColorsDemo.filter.whereClause())
.orderBy(.ascending(\.$hue))
static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$id))
)
static var filter: Modern.ColorsDemo.Filter = .all {
didSet {
try! Modern.ColorsDemo.palettesPublisher.refetch(
From<Modern.ColorsDemo.Palette>()
.sectionBy(\.$colorName)
.where(self.filter.whereClause())
.orderBy(.ascending(\.$hue))
)
}
}
}
}

View File

@@ -72,7 +72,9 @@ extension Menu {
Menu.ItemView(
title: "Pokedex API",
subtitle: "Importing JSON data from external source",
destination: { EmptyView() }
destination: {
Modern.PokedexDemo.MainView()
}
)
}
Section(header: Text("Classic (NSManagedObject subclasses)")) {
@@ -127,23 +129,11 @@ extension Menu {
}
.listStyle(GroupedListStyle())
.navigationBarTitle("CoreStore Demos")
Menu.DetailView()
Menu.PlaceholderView()
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
fileprivate struct DetailView: View {
var selectedDate: Date?
var body: some View {
Group {
Text("Detail view content goes here")
}
.navigationBarTitle(Text("Detail"))
}
}
}
#if DEBUG

View 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

View File

@@ -368,6 +368,18 @@ extension From where O: CoreStoreObject {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections
@@ -416,6 +428,48 @@ extension From where O: CoreStoreObject {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name

View File

@@ -106,6 +106,36 @@ extension SectionBy where O: NSManagedObject {
@available(macOS 10.12, *)
extension SectionBy where O: CoreStoreObject {
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
- parameter sectionKeyPath: the key path to use to group the objects into sections
*/
public init<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>) {
self.init(sectionKeyPath, { $0 })
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
- parameter sectionKeyPath: the key path to use to group the objects into sections
*/
public init<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>) {
self.init(sectionKeyPath, { $0 })
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
- parameter sectionKeyPath: the key path to use to group the objects into sections
*/
public init<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>) {
self.init(sectionKeyPath, { $0 })
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
@@ -158,6 +188,42 @@ extension SectionBy where O: CoreStoreObject {
self.init(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
*/
public init<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) {
self.init(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
*/
public init<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) {
self.init(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
*/
public init<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) {
self.init(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name