mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-14 21:23:43 +01:00
cleanup
This commit is contained in:
@@ -8,14 +8,14 @@
|
||||
|
||||
/* 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 */; };
|
||||
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 */; };
|
||||
B566C8E624ED6B98001134A1 /* NetworkImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E524ED6B98001134A1 /* NetworkImageView.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.PokemonDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift */; };
|
||||
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.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 */; };
|
||||
@@ -28,7 +28,7 @@
|
||||
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 */; };
|
||||
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, ); }; };
|
||||
@@ -46,28 +46,28 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */; };
|
||||
B5A391A824E90F1000E7E8BD /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A724E90F1000E7E8BD /* UIImage+Extensions.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 */; };
|
||||
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.PokemonDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */; };
|
||||
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */; };
|
||||
B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.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 */; };
|
||||
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -90,14 +90,14 @@
|
||||
|
||||
/* 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>"; };
|
||||
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>"; };
|
||||
B566C8E524ED6B98001134A1 /* NetworkImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkImageView.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.PokemonDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonDetails.swift; sourceTree = "<group>"; };
|
||||
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Details.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>"; };
|
||||
@@ -113,7 +113,7 @@
|
||||
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>"; };
|
||||
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; };
|
||||
@@ -127,28 +127,28 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.MainView.swift"; sourceTree = "<group>"; };
|
||||
B5A391A724E90F1000E7E8BD /* UIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.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>"; };
|
||||
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.PokemonDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonDisplay.swift; sourceTree = "<group>"; };
|
||||
B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonForm.swift; sourceTree = "<group>"; };
|
||||
B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonType.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>"; };
|
||||
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -271,7 +271,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */,
|
||||
B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */,
|
||||
B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */,
|
||||
B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */,
|
||||
B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */,
|
||||
B5A3916324E698B300E7E8BD /* Models */,
|
||||
@@ -318,7 +318,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */,
|
||||
B5A3918724E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift */,
|
||||
B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */,
|
||||
B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */,
|
||||
B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */,
|
||||
B5A3918424E7A53300E7E8BD /* Models */,
|
||||
@@ -339,7 +339,7 @@
|
||||
children = (
|
||||
B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */,
|
||||
B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */,
|
||||
B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */,
|
||||
B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */,
|
||||
B5A3919C24E8EE9000E7E8BD /* UIKit */,
|
||||
B5A3919B24E8EE8100E7E8BD /* SwiftUI */,
|
||||
B5A3919024E7E0B000E7E8BD /* Models */,
|
||||
@@ -359,9 +359,9 @@
|
||||
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 */,
|
||||
B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */,
|
||||
B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */,
|
||||
B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */,
|
||||
);
|
||||
name = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
@@ -371,10 +371,10 @@
|
||||
children = (
|
||||
B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */,
|
||||
B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */,
|
||||
B5A391A124E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.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 */,
|
||||
B5A391AD24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift */,
|
||||
);
|
||||
name = UIKit;
|
||||
sourceTree = "<group>";
|
||||
@@ -383,7 +383,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
|
||||
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */,
|
||||
B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */,
|
||||
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */,
|
||||
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */,
|
||||
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */,
|
||||
@@ -396,10 +396,10 @@
|
||||
B5A391B224E96B7400E7E8BD /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */,
|
||||
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift */,
|
||||
B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */,
|
||||
B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */,
|
||||
B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */,
|
||||
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */,
|
||||
B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */,
|
||||
B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */,
|
||||
B5A391B724E96E8600E7E8BD /* Attributes */,
|
||||
);
|
||||
name = Models;
|
||||
@@ -408,7 +408,7 @@
|
||||
B5A391B724E96E8600E7E8BD /* Attributes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift */,
|
||||
B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */,
|
||||
);
|
||||
name = Attributes;
|
||||
sourceTree = "<group>";
|
||||
@@ -487,12 +487,12 @@
|
||||
B5A391A824E90F1000E7E8BD /* UIImage+Extensions.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 */,
|
||||
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */,
|
||||
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */,
|
||||
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */,
|
||||
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */,
|
||||
B566C8E624ED6B98001134A1 /* NetworkImageView.swift in Sources */,
|
||||
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */,
|
||||
B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */,
|
||||
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */,
|
||||
@@ -501,37 +501,37 @@
|
||||
B5A3915E24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift in Sources */,
|
||||
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */,
|
||||
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */,
|
||||
B566C8E624ED6B98001134A1 /* NetworkImageView.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 */,
|
||||
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */,
|
||||
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */,
|
||||
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */,
|
||||
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */,
|
||||
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */,
|
||||
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift in Sources */,
|
||||
B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */,
|
||||
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */,
|
||||
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */,
|
||||
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */,
|
||||
B5A3918824E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.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 */,
|
||||
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.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 */,
|
||||
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 */,
|
||||
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 */,
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ extension Modern.ColorsDemo.UIKit {
|
||||
final class ListViewController: UITableViewController {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declatation below).
|
||||
⭐️ Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declaration below).
|
||||
*/
|
||||
private lazy var dataSource: DiffableDataSource.TableViewAdapter<Modern.ColorsDemo.Palette> = DeletionEnabledDataSource(
|
||||
tableView: self.tableView,
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Details
|
||||
|
||||
final class Details: CoreStoreObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Relationship("pokedexEntry", inverse: \.$details)
|
||||
var pokedexEntry: Modern.PokedexDemo.PokedexEntry?
|
||||
|
||||
@Field.Relationship("species")
|
||||
var species: Modern.PokedexDemo.Species?
|
||||
|
||||
@Field.Relationship("forms")
|
||||
var forms: [Modern.PokedexDemo.Form]
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@ extension Modern.PokedexDemo {
|
||||
do {
|
||||
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.textColor = UIColor.white
|
||||
nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
|
||||
nameLabel.numberOfLines = 0
|
||||
nameLabel.textAlignment = .center
|
||||
@@ -244,20 +245,20 @@ extension Modern.PokedexDemo {
|
||||
|
||||
return
|
||||
}
|
||||
self.pokemonDetails = newValue.snapshot?.$pokemonDetails
|
||||
self.details = newValue.snapshot?.$details
|
||||
|
||||
self.didUpdateData(animated: true)
|
||||
}
|
||||
|
||||
self.pokemonDetails = newValue?.snapshot?.$pokemonDetails
|
||||
self.details = newValue?.snapshot?.$details
|
||||
}
|
||||
}
|
||||
|
||||
private var pokemonDetails: ObjectPublisher<Modern.PokedexDemo.PokemonDetails>? {
|
||||
private var details: ObjectPublisher<Modern.PokedexDemo.Details>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.pokemonDetails
|
||||
let newValue = self.details
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
@@ -269,24 +270,24 @@ extension Modern.PokedexDemo {
|
||||
|
||||
return
|
||||
}
|
||||
let pokemonDetails = newValue.snapshot
|
||||
self.pokemonForm = pokemonDetails?.$pokemonForm
|
||||
self.pokemonDisplays = pokemonDetails?.$pokemonDisplays
|
||||
let details = newValue.snapshot
|
||||
self.species = details?.$species
|
||||
self.forms = details?.$forms
|
||||
|
||||
self.didUpdateData(animated: true)
|
||||
}
|
||||
|
||||
let pokemonDetails = newValue?.snapshot
|
||||
self.pokemonForm = pokemonDetails?.$pokemonForm
|
||||
self.pokemonDisplays = pokemonDetails?.$pokemonDisplays
|
||||
let details = newValue?.snapshot
|
||||
self.species = details?.$species
|
||||
self.forms = details?.$forms
|
||||
}
|
||||
}
|
||||
|
||||
private var pokemonForm: ObjectPublisher<Modern.PokedexDemo.PokemonForm>? {
|
||||
private var species: ObjectPublisher<Modern.PokedexDemo.Species>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.pokemonForm
|
||||
let newValue = self.species
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
@@ -299,19 +300,19 @@ extension Modern.PokedexDemo {
|
||||
}
|
||||
}
|
||||
|
||||
private var rotationCancellable: AnyCancellable?
|
||||
private var pokemonDisplays: [ObjectPublisher<Modern.PokedexDemo.PokemonDisplay>]? {
|
||||
private var formsRotationCancellable: AnyCancellable?
|
||||
private var forms: [ObjectPublisher<Modern.PokedexDemo.Form>]? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.pokemonDisplays
|
||||
let newValue = self.forms
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
self.pokemonDisplay = newValue?.first
|
||||
self.currentForm = newValue?.first
|
||||
|
||||
self.rotationCancellable = newValue.flatMap { newValue in
|
||||
self.formsRotationCancellable = newValue.flatMap { newValue in
|
||||
|
||||
guard !newValue.isEmpty else {
|
||||
|
||||
@@ -328,7 +329,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
return
|
||||
}
|
||||
self.pokemonDisplay = newValue[index % newValue.count]
|
||||
self.currentForm = newValue[index % newValue.count]
|
||||
self.didUpdateData(animated: true)
|
||||
}
|
||||
)
|
||||
@@ -336,11 +337,11 @@ extension Modern.PokedexDemo {
|
||||
}
|
||||
}
|
||||
|
||||
private var pokemonDisplay: ObjectPublisher<Modern.PokedexDemo.PokemonDisplay>? {
|
||||
private var currentForm: ObjectPublisher<Modern.PokedexDemo.Form>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.pokemonDisplay
|
||||
let newValue = self.currentForm
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
@@ -356,25 +357,25 @@ extension Modern.PokedexDemo {
|
||||
private func didUpdateData(animated: Bool) {
|
||||
|
||||
let pokedexEntry = self.pokedexEntry?.snapshot
|
||||
let pokemonForm = self.pokemonForm?.snapshot
|
||||
let pokemonDisplay = self.pokemonDisplay?.snapshot
|
||||
let species = self.species?.snapshot
|
||||
let currentForm = self.currentForm?.snapshot
|
||||
|
||||
self.placeholderLabel.text = pokedexEntry?.$id
|
||||
self.placeholderLabel.isHidden = pokemonForm != nil
|
||||
self.placeholderLabel.isHidden = species != nil
|
||||
|
||||
self.type1View.backgroundColor = pokemonForm?.$pokemonType1.color
|
||||
self.type1View.backgroundColor = species?.$pokemonType1.color
|
||||
?? UIColor.clear
|
||||
self.type1View.isHidden = pokemonForm == nil
|
||||
self.type1View.isHidden = species == nil
|
||||
|
||||
self.type2View.backgroundColor = pokemonForm?.$pokemonType2?.color
|
||||
?? pokemonForm?.$pokemonType1.color
|
||||
self.type2View.backgroundColor = species?.$pokemonType2?.color
|
||||
?? species?.$pokemonType1.color
|
||||
?? UIColor.clear
|
||||
self.type2View.isHidden = pokemonForm == nil
|
||||
self.type2View.isHidden = species == nil
|
||||
|
||||
self.nameLabel.text = pokemonDisplay?.$name ?? pokemonForm?.$name
|
||||
self.nameLabel.isHidden = pokemonDisplay == nil && pokemonForm == nil
|
||||
self.nameLabel.text = currentForm?.$name ?? species?.$name
|
||||
self.nameLabel.isHidden = currentForm == nil && species == nil
|
||||
|
||||
self.imageURL = pokemonDisplay?.$spriteURL
|
||||
self.imageURL = currentForm?.$spriteURL
|
||||
|
||||
guard animated else {
|
||||
|
||||
|
||||
@@ -14,50 +14,6 @@ extension Modern.PokedexDemo {
|
||||
|
||||
final class ListViewController: UICollectionViewController {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declatation below).
|
||||
*/
|
||||
private lazy var dataSource: DiffableDataSource.CollectionViewAdapter<Modern.PokedexDemo.PokedexEntry> = .init(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: Modern.PokedexDemo.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, pokedexEntry) in
|
||||
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! Modern.PokedexDemo.ItemCell
|
||||
cell.setPokedexEntry(pokedexEntry, service: self.service)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. 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 `dataSource.apply()` once.
|
||||
*/
|
||||
private func startObservingList() {
|
||||
|
||||
self.listPublisher.addObserver(self) { (listPublisher) in
|
||||
|
||||
self.dataSource.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
self.dataSource.apply(
|
||||
self.listPublisher.snapshot,
|
||||
animatingDifferences: false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ListPublisher`s safely remove deallocated observers automatically.
|
||||
*/
|
||||
deinit {
|
||||
|
||||
self.listPublisher.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
@@ -84,6 +40,17 @@ extension Modern.PokedexDemo {
|
||||
)
|
||||
super.init(collectionViewLayout: layout)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.listPublisher.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
@@ -92,6 +59,8 @@ extension Modern.PokedexDemo {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.collectionView.backgroundColor = UIColor.systemBackground
|
||||
|
||||
self.collectionView.register(
|
||||
Modern.PokedexDemo.ItemCell.self,
|
||||
forCellWithReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier
|
||||
@@ -106,10 +75,34 @@ extension Modern.PokedexDemo {
|
||||
private let service: Modern.PokedexDemo.Service
|
||||
private let listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
private lazy var dataSource: DiffableDataSource.CollectionViewAdapter<Modern.PokedexDemo.PokedexEntry> = .init(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: Modern.PokedexDemo.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, pokedexEntry) in
|
||||
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! Modern.PokedexDemo.ItemCell
|
||||
cell.setPokedexEntry(pokedexEntry, service: self.service)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
|
||||
private func startObservingList() {
|
||||
|
||||
fatalError()
|
||||
self.listPublisher.addObserver(self) { (listPublisher) in
|
||||
|
||||
self.dataSource.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
self.dataSource.apply(
|
||||
self.listPublisher.snapshot,
|
||||
animatingDifferences: false
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,6 @@ extension Modern.PokedexDemo {
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
|
||||
*/
|
||||
@ObservedObject
|
||||
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init() {
|
||||
@@ -44,7 +37,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
if pokedexEntries.isEmpty {
|
||||
|
||||
VStack(alignment: .center, spacing: 20) {
|
||||
VStack(alignment: .center, spacing: 30) {
|
||||
Text("This demo needs to make a network connection to download Pokedex entries")
|
||||
.multilineTextAlignment(.center)
|
||||
if self.service.isLoading {
|
||||
@@ -70,6 +63,9 @@ extension Modern.PokedexDemo {
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
@ObservedObject
|
||||
private var service: Modern.PokedexDemo.Service = .init()
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokemonDetails
|
||||
|
||||
final class PokemonDetails: CoreStoreObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Relationship("pokedexEntry", inverse: \.$pokemonDetails)
|
||||
var pokedexEntry: Modern.PokedexDemo.PokedexEntry?
|
||||
|
||||
@Field.Relationship("pokemonForm")
|
||||
var pokemonForm: Modern.PokedexDemo.PokemonForm?
|
||||
|
||||
@Field.Relationship("pokemonDisplays")
|
||||
var pokemonDisplays: [Modern.PokedexDemo.PokemonDisplay]
|
||||
}
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service
|
||||
|
||||
final class Service: ObservableObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
private(set) var isLoading: Bool = false {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var lastError: (error: Modern.PokedexDemo.Service.Error, retry: () -> Void)? {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
static func parseJSON<Output>(
|
||||
_ json: Any?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) 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),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static func parseJSON<JSONType, Output>(
|
||||
_ json: Any?,
|
||||
transformer: (JSONType) throws -> Output?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) throws -> Output {
|
||||
|
||||
switch json {
|
||||
|
||||
case let json as JSONType:
|
||||
let transformed = try transformer(json)
|
||||
if let json = transformed {
|
||||
|
||||
return json
|
||||
}
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: transformed),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
|
||||
case let any:
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: any),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPokedexEntries() {
|
||||
|
||||
self.cancellable["pokedexEntries"] = self.pokedexEntries
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.lastError = nil
|
||||
self.isLoading = true
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.isLoading = false
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
self.lastError = nil
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
self.lastError = (
|
||||
error: error,
|
||||
retry: { [weak self] in
|
||||
|
||||
self?.fetchPokedexEntries()
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
receiveValue: {}
|
||||
)
|
||||
}
|
||||
|
||||
func fetchDetails(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
self.fetchPokemonFormIfNeeded(for: pokedexEntry)
|
||||
}
|
||||
|
||||
private func fetchPokemonFormIfNeeded(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
guard let pokemonDetails = pokedexEntry.$pokemonDetails?.snapshot else {
|
||||
|
||||
return
|
||||
}
|
||||
if let pokedexForm = pokemonDetails.$pokemonForm?.snapshot {
|
||||
|
||||
self.fetchPokemonDisplayIfNeeded(for: pokedexForm)
|
||||
return
|
||||
}
|
||||
self.cancellable["pokemonForm.\(pokedexEntry.$id)"] = URLSession.shared
|
||||
.dataTaskPublisher(for: pokedexEntry.$pokemonFormURL)
|
||||
.mapError({ .networkError($0) })
|
||||
.flatMap(
|
||||
{ output in
|
||||
|
||||
return Future<ObjectSnapshot<Modern.PokedexDemo.PokemonForm>, Modern.PokedexDemo.Service.Error> { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Modern.PokedexDemo.PokemonForm in
|
||||
|
||||
let json: Dictionary<String, Any> = try Self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
guard let pokedexForm = try transaction.importUniqueObject(
|
||||
Into<Modern.PokedexDemo.PokemonForm>(),
|
||||
source: json
|
||||
) else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
pokemonDetails.asEditable(in: transaction)?.pokemonForm = pokedexForm
|
||||
return pokedexForm
|
||||
},
|
||||
success: { pokemonForm in
|
||||
|
||||
promise(.success(pokemonForm.asSnapshot(in: Modern.PokedexDemo.dataStack)!))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error):
|
||||
switch error {
|
||||
|
||||
case let error as Modern.PokedexDemo.Service.Error:
|
||||
promise(.failure(error))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.otherError(error)))
|
||||
}
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { pokemonForm in
|
||||
|
||||
self.fetchPokemonDisplayIfNeeded(for: pokemonForm)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func fetchPokemonDisplayIfNeeded(for pokemonForm: ObjectSnapshot<Modern.PokedexDemo.PokemonForm>) {
|
||||
|
||||
guard
|
||||
let pokemonDetails = pokemonForm.$pokemonDetails?.snapshot,
|
||||
pokemonDetails.$pokemonDisplays.isEmpty
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.cancellable["pokemonDisplay.\(pokemonForm.$id)"] = pokemonForm
|
||||
.$pokemonDisplayURLs
|
||||
.map(
|
||||
{ url in
|
||||
URLSession.shared
|
||||
.dataTaskPublisher(for: url)
|
||||
.mapError({ Modern.PokedexDemo.Service.Error.networkError($0) })
|
||||
.tryMap(
|
||||
{ output -> Dictionary<String, Any> in
|
||||
|
||||
try Self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
}
|
||||
)
|
||||
.mapError(
|
||||
{ error -> Modern.PokedexDemo.Service.Error in
|
||||
switch error {
|
||||
|
||||
case let error as Modern.PokedexDemo.Service.Error:
|
||||
return error
|
||||
|
||||
case let error:
|
||||
return Modern.PokedexDemo.Service.Error.otherError(error)
|
||||
}
|
||||
}
|
||||
)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
)
|
||||
.reduce(
|
||||
into: Just<[Dictionary<String, Any>]>([])
|
||||
.setFailureType(to: Modern.PokedexDemo.Service.Error.self)
|
||||
.eraseToAnyPublisher(),
|
||||
{ (result, publisher) in
|
||||
result = result
|
||||
.zip(publisher, { $0 + [$1] })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
)
|
||||
.flatMap(
|
||||
{ outputs in
|
||||
|
||||
return Future<Void, Modern.PokedexDemo.Service.Error> { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Void in
|
||||
|
||||
let pokemonDisplays = try transaction.importUniqueObjects(
|
||||
Into<Modern.PokedexDemo.PokemonDisplay>(),
|
||||
sourceArray: outputs
|
||||
)
|
||||
guard !pokemonDisplays.isEmpty else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
pokemonDetails.asEditable(in: transaction)?.pokemonDisplays = pokemonDisplays
|
||||
},
|
||||
success: {
|
||||
|
||||
promise(.success(()))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error):
|
||||
switch error {
|
||||
|
||||
case let error as Modern.PokedexDemo.Service.Error:
|
||||
promise(.failure(error))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.otherError(error)))
|
||||
}
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { output 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.enumerated().map { (index, json) in
|
||||
(index: index, json: json)
|
||||
}
|
||||
)
|
||||
},
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service.Error
|
||||
|
||||
enum Error: Swift.Error {
|
||||
|
||||
case networkError(URLError)
|
||||
case parseError(expected: Any.Type, actual: Any.Type, file: String)
|
||||
case saveError(CoreStoreError)
|
||||
case otherError(Swift.Error)
|
||||
case unexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ extension Modern {
|
||||
modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<Modern.PokedexDemo.PokedexEntry>("PokedexEntry"),
|
||||
Entity<Modern.PokedexDemo.PokemonDetails>("PokemonDetails"),
|
||||
Entity<Modern.PokedexDemo.PokemonForm>("PokemonForm"),
|
||||
Entity<Modern.PokedexDemo.PokemonDisplay>("PokemonDisplay")
|
||||
Entity<Modern.PokedexDemo.Details>("Details"),
|
||||
Entity<Modern.PokedexDemo.Species>("Species"),
|
||||
Entity<Modern.PokedexDemo.Form>("Form")
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,9 +9,12 @@ import UIKit
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokemonDisplay
|
||||
// MARK: - Modern.PokedexDemo.Form
|
||||
|
||||
final class PokemonDisplay: CoreStoreObject, ImportableUniqueObject {
|
||||
/**
|
||||
⭐️ Sample 1: This sample shows how to declare `CoreStoreObject` subclasses that implement `ImportableUniqueObject`. For this class the `ImportSource` is a JSON `Dictionary`.
|
||||
*/
|
||||
final class Form: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -25,8 +28,8 @@ extension Modern.PokedexDemo {
|
||||
var spriteURL: URL?
|
||||
|
||||
|
||||
@Field.Relationship("pokemonDetails", inverse: \.$pokemonDisplays)
|
||||
var pokemonDetails: Modern.PokedexDemo.PokemonDetails?
|
||||
@Field.Relationship("details", inverse: \.$forms)
|
||||
var details: Modern.PokedexDemo.Details?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
@@ -38,7 +41,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
typealias UniqueIDType = Int
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokemonDisplay.$id)
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.Form.$id)
|
||||
|
||||
var uniqueIDValue: UniqueIDType {
|
||||
|
||||
@@ -9,7 +9,10 @@ import CoreStore
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokedexEntry
|
||||
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: This sample shows how to declare `CoreStoreObject` subclasses that implement `ImportableUniqueObject`. For this class the `ImportSource` is a tuple.
|
||||
*/
|
||||
final class PokedexEntry: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
@@ -21,14 +24,14 @@ extension Modern.PokedexDemo {
|
||||
var id: String = ""
|
||||
|
||||
@Field.Stored(
|
||||
"pokemonFormURL",
|
||||
"speciesURL",
|
||||
dynamicInitialValue: { URL(string: "data:application/json,%7B%7D")! }
|
||||
)
|
||||
var pokemonFormURL: URL
|
||||
var speciesURL: URL
|
||||
|
||||
|
||||
@Field.Relationship("pokemonDetails")
|
||||
var pokemonDetails: Modern.PokedexDemo.PokemonDetails?
|
||||
@Field.Relationship("details")
|
||||
var details: Modern.PokedexDemo.Details?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
@@ -37,7 +40,8 @@ extension Modern.PokedexDemo {
|
||||
|
||||
func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||
|
||||
self.pokemonDetails = transaction.create(Into<Modern.PokedexDemo.PokemonDetails>())
|
||||
self.details = transaction.create(Into<Modern.PokedexDemo.Details>())
|
||||
|
||||
try self.update(from: source, in: transaction)
|
||||
}
|
||||
|
||||
@@ -64,7 +68,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
let json = source.json
|
||||
self.index = source.index
|
||||
self.pokemonFormURL = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
self.speciesURL = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Move
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Types that will be used with `@Field.Stored` need to implement both `ImportableAttributeType` and `FieldStorableType`. In this case, `RawRepresentable` types with primitive `RawValue`s have built-in implementations so we only have to declare conformance to `ImportableAttributeType` and `FieldStorableType`.
|
||||
*/
|
||||
enum PokemonType: String, CaseIterable, ImportableAttributeType, FieldStorableType {
|
||||
|
||||
// MARK: Internal
|
||||
@@ -0,0 +1,390 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service
|
||||
|
||||
final class Service: ObservableObject {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are tuples
|
||||
*/
|
||||
private static func importPokedexEntries(
|
||||
from output: URLSession.DataTaskPublisher.Output
|
||||
) -> Future<Void, Modern.PokedexDemo.Service.Error> {
|
||||
|
||||
return .init { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Void in
|
||||
|
||||
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"]
|
||||
)
|
||||
_ = try transaction.importUniqueObjects(
|
||||
Into<Modern.PokedexDemo.PokedexEntry>(),
|
||||
sourceArray: results.enumerated().map { (index, json) in
|
||||
(index: index, json: json)
|
||||
}
|
||||
)
|
||||
},
|
||||
success: { result in
|
||||
|
||||
promise(.success(result))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||
promise(.failure(error))
|
||||
|
||||
case .userError(let error):
|
||||
promise(.failure(.otherError(error)))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
|
||||
*/
|
||||
private static func importSpecies(
|
||||
for details: ObjectSnapshot<Modern.PokedexDemo.Details>,
|
||||
from output: URLSession.DataTaskPublisher.Output
|
||||
) -> Future<ObjectSnapshot<Modern.PokedexDemo.Species>, Modern.PokedexDemo.Service.Error> {
|
||||
|
||||
return .init { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Modern.PokedexDemo.Species in
|
||||
|
||||
let json: Dictionary<String, Any> = try self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
guard
|
||||
let species = try transaction.importUniqueObject(
|
||||
Into<Modern.PokedexDemo.Species>(),
|
||||
source: json
|
||||
)
|
||||
else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
details.asEditable(in: transaction)?.species = species
|
||||
return species
|
||||
},
|
||||
success: { species in
|
||||
|
||||
promise(.success(species.asSnapshot(in: Modern.PokedexDemo.dataStack)!))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||
promise(.failure(error))
|
||||
|
||||
case .userError(let error):
|
||||
promise(.failure(.otherError(error)))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
|
||||
*/
|
||||
private static func importForms(
|
||||
for details: ObjectSnapshot<Modern.PokedexDemo.Details>,
|
||||
from outputs: [URLSession.DataTaskPublisher.Output]
|
||||
) -> Future<Void, Modern.PokedexDemo.Service.Error> {
|
||||
|
||||
return .init { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Void in
|
||||
|
||||
let forms = try transaction.importUniqueObjects(
|
||||
Into<Modern.PokedexDemo.Form>(),
|
||||
sourceArray: outputs.map { output in
|
||||
|
||||
return try self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
}
|
||||
)
|
||||
guard !forms.isEmpty else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
details.asEditable(in: transaction)?.forms = forms
|
||||
},
|
||||
success: {
|
||||
|
||||
promise(.success(()))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||
promise(.failure(error))
|
||||
|
||||
case .userError(let error):
|
||||
promise(.failure(.otherError(error)))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
private(set) var isLoading: Bool = false {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var lastError: (error: Modern.PokedexDemo.Service.Error, retry: () -> Void)? {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
static func parseJSON<Output>(
|
||||
_ json: Any?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) 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),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static func parseJSON<JSONType, Output>(
|
||||
_ json: Any?,
|
||||
transformer: (JSONType) throws -> Output?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) throws -> Output {
|
||||
|
||||
switch json {
|
||||
|
||||
case let json as JSONType:
|
||||
let transformed = try transformer(json)
|
||||
if let json = transformed {
|
||||
|
||||
return json
|
||||
}
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: transformed),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
|
||||
case let any:
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: any),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPokedexEntries() {
|
||||
|
||||
self.cancellable["pokedexEntries"] = self.pokedexEntries
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.lastError = nil
|
||||
self.isLoading = true
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.isLoading = false
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
self.lastError = nil
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
self.lastError = (
|
||||
error: error,
|
||||
retry: { [weak self] in
|
||||
|
||||
self?.fetchPokedexEntries()
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
receiveValue: {}
|
||||
)
|
||||
}
|
||||
|
||||
func fetchDetails(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
self.fetchSpeciesIfNeeded(for: pokedexEntry)
|
||||
}
|
||||
|
||||
|
||||
// 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(Self.importPokedexEntries(from:))
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
private func fetchSpeciesIfNeeded(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
guard let details = pokedexEntry.$details?.snapshot else {
|
||||
|
||||
return
|
||||
}
|
||||
if let species = details.$species?.snapshot {
|
||||
|
||||
self.fetchFormsIfNeeded(for: species)
|
||||
return
|
||||
}
|
||||
self.cancellable["species.\(pokedexEntry.$id)"] = URLSession.shared
|
||||
.dataTaskPublisher(for: pokedexEntry.$speciesURL)
|
||||
.mapError({ .networkError($0) })
|
||||
.flatMap({ Self.importSpecies(for: details, from: $0) })
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { species in
|
||||
|
||||
self.fetchFormsIfNeeded(for: species)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func fetchFormsIfNeeded(for species: ObjectSnapshot<Modern.PokedexDemo.Species>) {
|
||||
|
||||
guard
|
||||
let details = species.$details?.snapshot,
|
||||
details.$forms.isEmpty
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
self.cancellable["forms.\(species.$id)"] = species
|
||||
.$formsURLs
|
||||
.map(
|
||||
{
|
||||
URLSession.shared
|
||||
.dataTaskPublisher(for: $0)
|
||||
.mapError({ Modern.PokedexDemo.Service.Error.networkError($0) })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
)
|
||||
.reduce(
|
||||
into: Just<[URLSession.DataTaskPublisher.Output]>([])
|
||||
.setFailureType(to: Modern.PokedexDemo.Service.Error.self)
|
||||
.eraseToAnyPublisher(),
|
||||
{ (result, publisher) in
|
||||
result = result
|
||||
.zip(publisher, { $0 + [$1] })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
)
|
||||
.flatMap({ Self.importForms(for: details, from: $0) })
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service.Error
|
||||
|
||||
enum Error: Swift.Error {
|
||||
|
||||
case networkError(URLError)
|
||||
case parseError(expected: Any.Type, actual: Any.Type, file: String)
|
||||
case saveError(CoreStoreError)
|
||||
case otherError(Swift.Error)
|
||||
case unexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,12 @@ import CoreStore
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokemonForm
|
||||
// MARK: - Modern.PokedexDemo.Species
|
||||
|
||||
final class PokemonForm: CoreStoreObject, ImportableUniqueObject {
|
||||
/**
|
||||
⭐️ Sample 1: This sample shows how to declare `CoreStoreObject` subclasses that implement `ImportableUniqueObject`. For this class the `ImportSource` is a JSON `Dictionary`.
|
||||
*/
|
||||
final class Species: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -50,14 +53,14 @@ extension Modern.PokedexDemo {
|
||||
|
||||
|
||||
@Field.Coded(
|
||||
"pokemonDisplayURLs",
|
||||
"formsURLs",
|
||||
coder: FieldCoders.Json.self
|
||||
)
|
||||
var pokemonDisplayURLs: [URL] = []
|
||||
var formsURLs: [URL] = []
|
||||
|
||||
|
||||
@Field.Relationship("pokemonDetails", inverse: \.$pokemonForm)
|
||||
var pokemonDetails: Modern.PokedexDemo.PokemonDetails?
|
||||
@Field.Relationship("details", inverse: \.$species)
|
||||
var details: Modern.PokedexDemo.Details?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
@@ -69,7 +72,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
typealias UniqueIDType = Int
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokemonForm.$id)
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.Species.$id)
|
||||
|
||||
var uniqueIDValue: UniqueIDType {
|
||||
|
||||
@@ -129,11 +132,8 @@ extension Modern.PokedexDemo {
|
||||
}
|
||||
}
|
||||
|
||||
self.pokemonDisplayURLs = try (Service.parseJSON(json["forms"]) as [Dictionary<String, Any>]).map { json in
|
||||
|
||||
let pokemonDisplayURL = try Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
return pokemonDisplayURL
|
||||
}
|
||||
self.formsURLs = try (Service.parseJSON(json["forms"]) as [Dictionary<String, Any>])
|
||||
.map({ try Service.parseJSON($0["url"], transformer: URL.init(string:)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user