added demo for classic ListMonitor

This commit is contained in:
John Estropia
2020-08-30 20:16:01 +09:00
parent 007da014f8
commit 8d7f282743
63 changed files with 1463 additions and 102 deletions

View File

@@ -25,7 +25,7 @@
B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915224E6537F00E7E8BD /* Menu.ItemView.swift */; };
B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915824E685EC00E7E8BD /* Classic.swift */; };
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915A24E685FE00E7E8BD /* Modern.swift */; };
B5A3915E24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */; };
B5A3915E24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915D24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift */; };
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */; };
B5A3916224E697BA00E7E8BD /* ⭐Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* ⭐Modern.PlacemarksDemo.MainView.swift */; };
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */; };
@@ -66,6 +66,16 @@
B5A391B424E96C0A00E7E8BD /* ⭐Modern.PokedexDemo.Form.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* ⭐Modern.PokedexDemo.Form.swift */; };
B5A391B924E96F8500E7E8BD /* ⭐Modern.PokedexDemo.Species.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B824E96F8500E7E8BD /* ⭐Modern.PokedexDemo.Species.swift */; };
B5A391BB24E970A400E7E8BD /* ⭐Modern.PokedexDemo.PokemonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391BA24E970A400E7E8BD /* ⭐Modern.PokedexDemo.PokemonType.swift */; };
B5A543D724FB7478000DC5E3 /* Classic.ColorsDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543D624FB7478000DC5E3 /* Classic.ColorsDemo.swift */; };
B5A543DB24FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5A543D924FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld */; };
B5A543DD24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543DC24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift */; };
B5A543E724FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543E624FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift */; };
B5A543E924FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543E824FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift */; };
B5A543EB24FB84AF000DC5E3 /* ⭐Classic.ColorsDemo.DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543EA24FB84AF000DC5E3 /* ⭐Classic.ColorsDemo.DetailViewController.swift */; };
B5A543ED24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543EC24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift */; };
B5A543EF24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543EE24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift */; };
B5A543F124FB84DD000DC5E3 /* ⭐Classic.ColorsDemo.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543F024FB84DD000DC5E3 /* ⭐Classic.ColorsDemo.ListViewController.swift */; };
B5A543F324FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A543F224FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift */; };
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */; };
/* End PBXBuildFile section */
@@ -108,7 +118,7 @@
B5A3915224E6537F00E7E8BD /* Menu.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.ItemView.swift; sourceTree = "<group>"; };
B5A3915824E685EC00E7E8BD /* Classic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.swift; sourceTree = "<group>"; };
B5A3915A24E685FE00E7E8BD /* Modern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.swift; sourceTree = "<group>"; };
B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.swift; sourceTree = "<group>"; };
B5A3915D24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PlacemarksDemo.swift"; sourceTree = "<group>"; };
B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.MapView.swift; sourceTree = "<group>"; };
B5A3916124E697BA00E7E8BD /* ⭐Modern.PlacemarksDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Modern.PlacemarksDemo.MainView.swift"; sourceTree = "<group>"; };
B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.Place.swift; sourceTree = "<group>"; };
@@ -145,6 +155,16 @@
B5A391B324E96C0A00E7E8BD /* ⭐Modern.PokedexDemo.Form.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Modern.PokedexDemo.Form.swift"; sourceTree = "<group>"; };
B5A391B824E96F8500E7E8BD /* ⭐Modern.PokedexDemo.Species.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Modern.PokedexDemo.Species.swift"; sourceTree = "<group>"; };
B5A391BA24E970A400E7E8BD /* ⭐Modern.PokedexDemo.PokemonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Modern.PokedexDemo.PokemonType.swift"; sourceTree = "<group>"; };
B5A543D624FB7478000DC5E3 /* Classic.ColorsDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.swift; sourceTree = "<group>"; };
B5A543DA24FB7513000DC5E3 /* ColorsDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ColorsDemo.xcdatamodel; sourceTree = "<group>"; };
B5A543DC24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.Palette.swift; sourceTree = "<group>"; };
B5A543E624FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.Filter.swift; sourceTree = "<group>"; };
B5A543E824FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.DetailView.swift; sourceTree = "<group>"; };
B5A543EA24FB84AF000DC5E3 /* ⭐Classic.ColorsDemo.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Classic.ColorsDemo.DetailViewController.swift"; sourceTree = "<group>"; };
B5A543EC24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.MainView.swift; sourceTree = "<group>"; };
B5A543EE24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.LIstView.swift; sourceTree = "<group>"; };
B5A543F024FB84DD000DC5E3 /* ⭐Classic.ColorsDemo.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Classic.ColorsDemo.ListViewController.swift"; sourceTree = "<group>"; };
B5A543F224FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classic.ColorsDemo.ItemCell.swift; sourceTree = "<group>"; };
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -170,7 +190,7 @@
isa = PBXGroup;
children = (
B5A3913D24E62C6C00E7E8BD /* Metadata */,
B5A3911B24E5429200E7E8BD /* Sources */,
B5A3911B24E5429200E7E8BD /* ⭐️Sources */,
B5A3913E24E62CB200E7E8BD /* Resources */,
B5A3911A24E5429200E7E8BD /* Products */,
B5A3916624E698F900E7E8BD /* Frameworks */,
@@ -185,15 +205,15 @@
name = Products;
sourceTree = "<group>";
};
B5A3911B24E5429200E7E8BD /* Sources */ = {
B5A3911B24E5429200E7E8BD /* ⭐️Sources */ = {
isa = PBXGroup;
children = (
B5A3911C24E5429200E7E8BD /* AppDelegate.swift */,
B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */,
B5A3915524E6858A00E7E8BD /* Demos */,
B5A3915524E6858A00E7E8BD /* ⭐️Demos */,
B5A3917A24E6A75F00E7E8BD /* Helpers */,
);
path = Sources;
path = "⭐️Sources";
sourceTree = "<group>";
};
B5A3912424E5429300E7E8BD /* Preview Content */ = {
@@ -234,45 +254,46 @@
path = Menu;
sourceTree = "<group>";
};
B5A3915524E6858A00E7E8BD /* Demos */ = {
B5A3915524E6858A00E7E8BD /* ⭐️Demos */ = {
isa = PBXGroup;
children = (
B5A3915624E685B700E7E8BD /* Modern */,
B5A3915724E685D300E7E8BD /* Classic */,
B5A3915624E685B700E7E8BD /* ⭐️Modern */,
B5A3915724E685D300E7E8BD /* ⭐️Classic */,
);
path = Demos;
path = "⭐️Demos";
sourceTree = "<group>";
};
B5A3915624E685B700E7E8BD /* Modern */ = {
B5A3915624E685B700E7E8BD /* ⭐️Modern */ = {
isa = PBXGroup;
children = (
B5A3915A24E685FE00E7E8BD /* Modern.swift */,
B5A3915C24E6921E00E7E8BD /* PlacemarksDemo */,
B5A3918124E7A1EF00E7E8BD /* TimeZonesDemo */,
B5A3918D24E7DE7A00E7E8BD /* ColorsDemo */,
B5A391AF24E96AD600E7E8BD /* PokedexDemo */,
B5A3915C24E6921E00E7E8BD /* ⭐️PlacemarksDemo */,
B5A3918124E7A1EF00E7E8BD /* ⭐️TimeZonesDemo */,
B5A3918D24E7DE7A00E7E8BD /* ⭐️ColorsDemo */,
B5A391AF24E96AD600E7E8BD /* ⭐️PokedexDemo */,
);
path = Modern;
path = "⭐️Modern";
sourceTree = "<group>";
};
B5A3915724E685D300E7E8BD /* Classic */ = {
B5A3915724E685D300E7E8BD /* ⭐️Classic */ = {
isa = PBXGroup;
children = (
B5A3915824E685EC00E7E8BD /* Classic.swift */,
B5A543D524FB73B2000DC5E3 /* ⭐ColorsDemo */,
);
path = Classic;
path = "⭐️Classic";
sourceTree = "<group>";
};
B5A3915C24E6921E00E7E8BD /* PlacemarksDemo */ = {
B5A3915C24E6921E00E7E8BD /* ⭐️PlacemarksDemo */ = {
isa = PBXGroup;
children = (
B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */,
B5A3915D24E6922E00E7E8BD /* ⭐️Modern.PlacemarksDemo.swift */,
B5A3916124E697BA00E7E8BD /* ⭐Modern.PlacemarksDemo.MainView.swift */,
B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */,
B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */,
B5A3916324E698B300E7E8BD /* Models */,
);
path = PlacemarksDemo;
path = "⭐️PlacemarksDemo";
sourceTree = "<group>";
};
B5A3916324E698B300E7E8BD /* Models */ = {
@@ -308,7 +329,7 @@
path = Helpers;
sourceTree = "<group>";
};
B5A3918124E7A1EF00E7E8BD /* TimeZonesDemo */ = {
B5A3918124E7A1EF00E7E8BD /* ⭐️TimeZonesDemo */ = {
isa = PBXGroup;
children = (
B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */,
@@ -317,7 +338,7 @@
B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */,
B5A3918424E7A53300E7E8BD /* Models */,
);
path = TimeZonesDemo;
path = "⭐️TimeZonesDemo";
sourceTree = "<group>";
};
B5A3918424E7A53300E7E8BD /* Models */ = {
@@ -328,17 +349,17 @@
name = Models;
sourceTree = "<group>";
};
B5A3918D24E7DE7A00E7E8BD /* ColorsDemo */ = {
B5A3918D24E7DE7A00E7E8BD /* ⭐️ColorsDemo */ = {
isa = PBXGroup;
children = (
B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */,
B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */,
B5A391A524E8F4EA00E7E8BD /* ⭐Modern.ColorsDemo.MainView.swift */,
B5A3919C24E8EE9000E7E8BD /* UIKit */,
B5A3919B24E8EE8100E7E8BD /* SwiftUI */,
B5A3919C24E8EE9000E7E8BD /* ⭐️UIKit */,
B5A3919B24E8EE8100E7E8BD /* ⭐️SwiftUI */,
B5A3919024E7E0B000E7E8BD /* Models */,
);
path = ColorsDemo;
path = "⭐️ColorsDemo";
sourceTree = "<group>";
};
B5A3919024E7E0B000E7E8BD /* Models */ = {
@@ -349,7 +370,7 @@
name = Models;
sourceTree = "<group>";
};
B5A3919B24E8EE8100E7E8BD /* SwiftUI */ = {
B5A3919B24E8EE8100E7E8BD /* ⭐️SwiftUI */ = {
isa = PBXGroup;
children = (
B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */,
@@ -357,10 +378,10 @@
B5A3919524E7E4AC00E7E8BD /* ⭐Modern.ColorsDemo.SwiftUI.ItemView.swift */,
B5A3919924E8207A00E7E8BD /* ⭐Modern.ColorsDemo.SwiftUI.DetailView.swift */,
);
name = SwiftUI;
name = "⭐️SwiftUI";
sourceTree = "<group>";
};
B5A3919C24E8EE9000E7E8BD /* UIKit */ = {
B5A3919C24E8EE9000E7E8BD /* ⭐️UIKit */ = {
isa = PBXGroup;
children = (
B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */,
@@ -370,10 +391,10 @@
B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */,
B5A391AD24E9150F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.DetailViewController.swift */,
);
name = UIKit;
name = "⭐️UIKit";
sourceTree = "<group>";
};
B5A391AF24E96AD600E7E8BD /* PokedexDemo */ = {
B5A391AF24E96AD600E7E8BD /* ⭐️PokedexDemo */ = {
isa = PBXGroup;
children = (
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
@@ -382,29 +403,54 @@
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */,
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */,
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */,
B5A391B224E96B7400E7E8BD /* Models */,
B5A391B224E96B7400E7E8BD /* ⭐️Models */,
);
path = PokedexDemo;
path = "⭐️PokedexDemo";
sourceTree = "<group>";
};
B5A391B224E96B7400E7E8BD /* Models */ = {
B5A391B224E96B7400E7E8BD /* ⭐️Models */ = {
isa = PBXGroup;
children = (
B531EFE824EB5A52005F247D /* ⭐Modern.PokedexDemo.PokedexEntry.swift */,
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */,
B5A391B824E96F8500E7E8BD /* ⭐Modern.PokedexDemo.Species.swift */,
B5A391B324E96C0A00E7E8BD /* ⭐Modern.PokedexDemo.Form.swift */,
B5A391B724E96E8600E7E8BD /* Attributes */,
B5A391B724E96E8600E7E8BD /* ⭐️Attributes */,
);
name = Models;
name = "⭐️Models";
sourceTree = "<group>";
};
B5A391B724E96E8600E7E8BD /* Attributes */ = {
B5A391B724E96E8600E7E8BD /* ⭐️Attributes */ = {
isa = PBXGroup;
children = (
B5A391BA24E970A400E7E8BD /* ⭐Modern.PokedexDemo.PokemonType.swift */,
);
name = Attributes;
name = "⭐️Attributes";
sourceTree = "<group>";
};
B5A543D524FB73B2000DC5E3 /* ⭐ColorsDemo */ = {
isa = PBXGroup;
children = (
B5A543D624FB7478000DC5E3 /* Classic.ColorsDemo.swift */,
B5A543E624FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift */,
B5A543EC24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift */,
B5A543EE24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift */,
B5A543F024FB84DD000DC5E3 /* ⭐Classic.ColorsDemo.ListViewController.swift */,
B5A543F224FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift */,
B5A543E824FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift */,
B5A543EA24FB84AF000DC5E3 /* ⭐Classic.ColorsDemo.DetailViewController.swift */,
B5A543D824FB7483000DC5E3 /* Models */,
);
path = "⭐ColorsDemo";
sourceTree = "<group>";
};
B5A543D824FB7483000DC5E3 /* Models */ = {
isa = PBXGroup;
children = (
B5A543D924FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld */,
B5A543DC24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift */,
);
name = Models;
sourceTree = "<group>";
};
/* End PBXGroup section */
@@ -484,19 +530,25 @@
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */,
B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */,
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */,
B5A543DB24FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld in Sources */,
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */,
B5A543D724FB7478000DC5E3 /* Classic.ColorsDemo.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 */,
B5A543EF24FB84D1000DC5E3 /* Classic.ColorsDemo.LIstView.swift in Sources */,
B5A3915E24E6922E00E7E8BD /* ⭐Modern.PlacemarksDemo.swift in Sources */,
B5A543F324FB84EC000DC5E3 /* Classic.ColorsDemo.ItemCell.swift in Sources */,
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */,
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */,
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */,
B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */,
B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */,
B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */,
B5A543E724FB82BB000DC5E3 /* Classic.ColorsDemo.Filter.swift in Sources */,
B5A543ED24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift in Sources */,
B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */,
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */,
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */,
@@ -510,6 +562,7 @@
B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */,
B5A391A624E8F4EA00E7E8BD /* ⭐Modern.ColorsDemo.MainView.swift in Sources */,
B5A3916224E697BA00E7E8BD /* ⭐Modern.PlacemarksDemo.MainView.swift in Sources */,
B5A543EB24FB84AF000DC5E3 /* ⭐Classic.ColorsDemo.DetailViewController.swift in Sources */,
B5A391B424E96C0A00E7E8BD /* ⭐Modern.PokedexDemo.Form.swift in Sources */,
B531EFE924EB5A53005F247D /* ⭐Modern.PokedexDemo.PokedexEntry.swift in Sources */,
B5A391BB24E970A400E7E8BD /* ⭐Modern.PokedexDemo.PokemonType.swift in Sources */,
@@ -519,11 +572,14 @@
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */,
B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */,
B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */,
B5A543E924FB84A1000DC5E3 /* Classic.ColorsDemo.DetailView.swift in Sources */,
B5A3919A24E8207A00E7E8BD /* ⭐Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */,
B5A543F124FB84DD000DC5E3 /* ⭐Classic.ColorsDemo.ListViewController.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 */,
B5A543DD24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -717,6 +773,19 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
B5A543D924FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
B5A543DA24FB7513000DC5E3 /* ColorsDemo.xcdatamodel */,
);
currentVersion = B5A543DA24FB7513000DC5E3 /* ColorsDemo.xcdatamodel */;
path = Classic.ColorsDemo.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = B5A3910F24E5424E00E7E8BD /* Project object */;
}

View File

@@ -1,9 +1,6 @@
//
// ImageDownloader.swift
// Demo
//
// Created by John Rommel Estropia on 2020/08/29.
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Foundation
import UIKit

View File

@@ -79,34 +79,23 @@ extension Menu {
}
Section(header: Text("Classic (NSManagedObject subclasses)")) {
Menu.ItemView(
title: "Placemarks (Swift)",
title: "Placemarks",
subtitle: "Making changes using transactions in Swift",
destination: { EmptyView() }
)
Menu.ItemView(
title: "Placemarks (Objective-C)",
subtitle: "Making changes using transactions in Objective-C",
destination: { EmptyView() }
)
.disabled(true)
Menu.ItemView(
title: "Time Zones",
subtitle: "Fetching objects and Querying raw values",
destination: { EmptyView() }
)
.disabled(true)
Menu.ItemView(
title: "Colors (Swift)",
subtitle: "Observing list changes and single-object changes in Swift",
destination: { EmptyView() }
)
Menu.ItemView(
title: "Colors (Objective-C)",
subtitle: "Observing list changes and single-object changes in Objective-C",
destination: { EmptyView() }
)
Menu.ItemView(
title: "Pokedex API",
subtitle: "Importing JSON data from external source",
destination: { EmptyView() }
title: "Colors",
subtitle: "Observing list changes and single-object changes using ListMonitor",
destination: {
Classic.ColorsDemo.MainView()
}
)
}
Section(header: Text("Advanced")) {

View File

@@ -0,0 +1,79 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Combine
import CoreStore
import SwiftUI
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.DetailView
struct DetailView: UIViewControllerRepresentable {
// MARK: Internal
init(_ palette: ObjectMonitor<Classic.ColorsDemo.Palette>) {
self.palette = palette
}
// MARK: UIViewControllerRepresentable
typealias UIViewControllerType = Classic.ColorsDemo.DetailViewController
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
return UIViewControllerType(self.palette)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {
uiViewController.palette = self.palette
}
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
func makeCoordinator() -> ObjectMonitor<Classic.ColorsDemo.Palette> {
return self.palette
}
// MARK: Private
private let palette: ObjectMonitor<Classic.ColorsDemo.Palette>
}
}
#if DEBUG
struct _Demo_Classic_ColorsDemo_DetailView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
try! Classic.ColorsDemo.dataStack.perform(
synchronous: { transaction in
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
return
}
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
palette.setRandomHue()
}
)
return Classic.ColorsDemo.DetailView(
Classic.ColorsDemo.dataStack.monitorObject(
Classic.ColorsDemo.palettesMonitor[0, 0]
)
)
}
}
#endif

View File

@@ -0,0 +1,36 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.Filter
enum Filter: String, CaseIterable {
case all = "All Colors"
case light = "Light Colors"
case dark = "Dark Colors"
func next() -> Filter {
let allCases = Self.allCases
return allCases[(allCases.firstIndex(of: self)! + 1) % allCases.count]
}
func whereClause() -> Where<Classic.ColorsDemo.Palette> {
switch self {
case .all: return .init()
case .light: return (\.brightness >= 0.9)
case .dark: return (\.brightness <= 0.4)
}
}
}
}

View File

@@ -0,0 +1,28 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import UIKit
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.ItemCell
final class ItemCell: UITableViewCell {
// MARK: Internal
static let reuseIdentifier: String = NSStringFromClass(Classic.ColorsDemo.ItemCell.self)
func setPalette(_ palette: Classic.ColorsDemo.Palette) {
self.contentView.backgroundColor = palette.color
self.textLabel?.text = palette.colorText
self.textLabel?.textColor = palette.brightness > 0.6 ? .black : .white
}
}
}

View File

@@ -0,0 +1,89 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import SwiftUI
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.ListView
struct ListView: UIViewControllerRepresentable {
// MARK: Internal
init(
listMonitor: ListMonitor<Classic.ColorsDemo.Palette>,
onPaletteTapped: @escaping (Classic.ColorsDemo.Palette) -> Void
) {
self.listMonitor = listMonitor
self.onPaletteTapped = onPaletteTapped
}
// MARK: UIViewControllerRepresentable
typealias UIViewControllerType = Classic.ColorsDemo.ListViewController
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
return UIViewControllerType(
listMonitor: self.listMonitor,
onPaletteTapped: self.onPaletteTapped
)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {
uiViewController.setEditing(
context.environment.editMode?.wrappedValue.isEditing == true,
animated: true
)
}
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
// MARK: Private
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
private let onPaletteTapped: (Classic.ColorsDemo.Palette) -> Void
}
}
#if DEBUG
struct _Demo_Classic_ColorsDemo_ListView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
let minimumSamples = 10
try! Classic.ColorsDemo.dataStack.perform(
synchronous: { transaction in
let missing = minimumSamples
- (try transaction.fetchCount(From<Classic.ColorsDemo.Palette>()))
guard missing > 0 else {
return
}
for _ in 0..<missing {
let palette = transaction.create(Into<Classic.ColorsDemo.Palette>())
palette.setRandomHue()
}
}
)
return Classic.ColorsDemo.ListView(
listMonitor: Classic.ColorsDemo.palettesMonitor,
onPaletteTapped: { _ in }
)
}
}
#endif

View File

@@ -0,0 +1,247 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import SwiftUI
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.MainView
struct MainView: View {
// MARK: Internal
init() {
let listMonitor = Classic.ColorsDemo.palettesMonitor
self.listMonitor = listMonitor
self.listHelper = .init(listMonitor: listMonitor)
self._filter = Binding(
get: { Classic.ColorsDemo.filter },
set: { Classic.ColorsDemo.filter = $0 }
)
}
// MARK: View
var body: some View {
let detailView: AnyView
if let selectedObject = self.listHelper.selectedObject() {
detailView = AnyView(
Classic.ColorsDemo.DetailView(selectedObject)
)
}
else {
detailView = AnyView(EmptyView())
}
let listMonitor = self.listMonitor
return VStack(spacing: 0) {
Classic.ColorsDemo.ListView
.init(
listMonitor: listMonitor,
onPaletteTapped: {
self.listHelper.setSelectedPalette($0)
}
)
.navigationBarTitle(
Text("Colors (\(self.listHelper.count) objects)")
)
.frame(minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.vertical)
detailView
.edgesIgnoringSafeArea(.all)
.frame(minHeight: 0, maxHeight: .infinity)
}
.navigationBarItems(
leading: HStack {
EditButton()
Button(
action: { self.clearColors() },
label: { Text("Clear") }
)
},
trailing: HStack {
Button(
action: { self.changeFilter() },
label: { Text(self.filter.rawValue) }
)
Button(
action: { self.shuffleColors() },
label: { Text("Shuffle") }
)
Button(
action: { self.addColor() },
label: { Text("Add") }
)
}
)
}
// MARK: Private
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
@ObservedObject
private var listHelper: ListHelper
@Binding
private var filter: Classic.ColorsDemo.Filter
private func changeFilter() {
Classic.ColorsDemo.filter = Classic.ColorsDemo.filter.next()
}
private func clearColors() {
Classic.ColorsDemo.dataStack.perform(
asynchronous: { transaction in
try transaction.deleteAll(From<Classic.ColorsDemo.Palette>())
},
completion: { _ in }
)
}
private func addColor() {
Classic.ColorsDemo.dataStack.perform(
asynchronous: { transaction in
_ = transaction.create(Into<Classic.ColorsDemo.Palette>())
},
completion: { _ in }
)
}
private func shuffleColors() {
Classic.ColorsDemo.dataStack.perform(
asynchronous: { transaction in
for palette in try transaction.fetchAll(From<Classic.ColorsDemo.Palette>()) {
palette.setRandomHue()
}
},
completion: { _ in }
)
}
// MARK: - Classic.ColorsDemo.MainView.ListHelper
fileprivate final class ListHelper: ObservableObject, ListObjectObserver {
// MARK: FilePrivate
fileprivate private(set) var count: Int = 0
fileprivate init(listMonitor: ListMonitor<Classic.ColorsDemo.Palette>) {
listMonitor.addObserver(self)
self.count = listMonitor.numberOfObjects()
}
fileprivate func selectedObject() -> ObjectMonitor<Classic.ColorsDemo.Palette>? {
return self.selectedPalette.flatMap {
guard !$0.isDeleted else {
return nil
}
return Classic.ColorsDemo.dataStack.monitorObject($0)
}
}
fileprivate func setSelectedPalette(_ palette: Classic.ColorsDemo.Palette?) {
guard self.selectedPalette != palette else {
return
}
self.objectWillChange.send()
if let palette = palette, !palette.isDeleted {
self.selectedPalette = palette
}
else {
self.selectedPalette = nil
}
}
// MARK: ListObserver
typealias ListEntityType = Classic.ColorsDemo.Palette
func listMonitorDidChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
self.objectWillChange.send()
self.count = monitor.numberOfObjects()
}
func listMonitorDidRefetch(_ monitor: ListMonitor<ListEntityType>) {
self.objectWillChange.send()
self.count = monitor.numberOfObjects()
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>, didDeleteObject object: Classic.ColorsDemo.Palette, fromIndexPath indexPath: IndexPath) {
if self.selectedPalette == object {
self.setSelectedPalette(nil)
}
}
// MARK: Private
private var selectedPalette: Classic.ColorsDemo.Palette?
}
}
}
#if DEBUG
struct _Demo_Classic_ColorsDemo_MainView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
let minimumSamples = 10
try! Classic.ColorsDemo.dataStack.perform(
synchronous: { transaction in
let missing = minimumSamples
- (try transaction.fetchCount(From<Classic.ColorsDemo.Palette>()))
guard missing > 0 else {
return
}
for _ in 0..<missing {
let palette = transaction.create(Into<Classic.ColorsDemo.Palette>())
palette.setRandomHue()
}
}
)
return Classic.ColorsDemo.MainView()
}
}
#endif

View File

@@ -0,0 +1,101 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreData
import UIKit
// MARK: - Classic.ColorsDemo.Palette
@objc(Classic_ColorsDemo_Palette)
final class Classic_ColorsDemo_Palette: NSManagedObject {
// MARK: Internal
@NSManaged
dynamic var hue: Float
@NSManaged
dynamic var saturation: Float
@NSManaged
dynamic var brightness: Float
@objc
dynamic var colorGroup: String! {
let key = #keyPath(colorGroup)
if case let value as String = self.getValue(forKvcKey: key) {
return value
}
let newValue: String
switch self.hue * 359 {
case 0 ..< 20: newValue = "Lower Reds"
case 20 ..< 57: newValue = "Oranges and Browns"
case 57 ..< 90: newValue = "Yellow-Greens"
case 90 ..< 159: newValue = "Greens"
case 159 ..< 197: newValue = "Blue-Greens"
case 197 ..< 241: newValue = "Blues"
case 241 ..< 297: newValue = "Violets"
case 297 ..< 331: newValue = "Magentas"
default: newValue = "Upper Reds"
}
self.setPrimitiveValue(newValue, forKey: key)
return newValue
}
var color: UIColor {
let newValue = UIColor(
hue: CGFloat(self.hue),
saturation: CGFloat(self.saturation),
brightness: CGFloat(self.brightness),
alpha: 1.0
)
return newValue
}
var colorText: String {
let newValue: String = "H: \(self.hue * 359)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%"
return newValue
}
func setRandomHue() {
self.hue = Self.randomHue()
}
// MARK: NSManagedObject
public override func awakeFromInsert() {
super.awakeFromInsert()
self.hue = Self.randomHue()
self.saturation = Self.randomSaturation()
self.brightness = Self.randomBrightness()
}
// MARK: Private
private static func randomHue() -> Float {
return Float.random(in: 0.0 ... 1.0)
}
private static func randomSaturation() -> Float {
return Float.random(in: 0.4 ... 1.0)
}
private static func randomBrightness() -> Float {
return Float.random(in: 0.0 ... 1.0)
}
}

View File

@@ -0,0 +1,59 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Classic
extension Classic {
// MARK: - Classic.ColorsDemo
/**
Sample usages for observing lists or single instances of `NSManagedObject`s
*/
enum ColorsDemo {
// MARK: Internal
typealias Palette = Classic_ColorsDemo_Palette
static let dataStack: DataStack = {
let dataStack = DataStack(
xcodeModelName: "Classic.ColorsDemo",
bundle: Bundle(for: Palette.self)
)
/**
- Important: `addStorageAndWait(_:)` was used here to simplify initializing the demo, but in practice the asynchronous function variants are recommended.
*/
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "Classic.ColorsDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return dataStack
}()
static let palettesMonitor: ListMonitor<Classic.ColorsDemo.Palette> = Classic.ColorsDemo.dataStack.monitorSectionedList(
From<Classic.ColorsDemo.Palette>()
.sectionBy(\.colorGroup)
.where(Classic.ColorsDemo.filter.whereClause())
.orderBy(.ascending(\.hue))
)
static var filter: Classic.ColorsDemo.Filter = .all {
didSet {
Classic.ColorsDemo.palettesMonitor.refetch(
self.filter.whereClause(),
OrderBy<Classic.ColorsDemo.Palette>(.ascending(\.hue))
)
}
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Palette" representedClassName="Classic_ColorsDemo_Palette" syncable="YES">
<attribute name="brightness" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="colorGroup" optional="YES" transient="YES" attributeType="String"/>
<attribute name="hue" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="saturation" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Palette" positionX="-63" positionY="-18" width="128" height="103"/>
</elements>
</model>

View File

@@ -0,0 +1,291 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import UIKit
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.DetailViewController
final class DetailViewController: UIViewController, ObjectObserver {
/**
Sample 1: We can normally use `ObjectPublisher` directly, which is simpler. But for this demo, we will be using `ObjectMonitor` instead because we need to keep track of which properties change to prevent our `UISlider` from stuttering. Refer to the `objectMonitor(_:didUpdateObject:changedPersistentKeys:)` implementation below.
*/
var palette: ObjectMonitor<Classic.ColorsDemo.Palette> {
didSet {
oldValue.removeObserver(self)
self.startMonitoringObject()
}
}
init(_ palette: ObjectMonitor<Classic.ColorsDemo.Palette>) {
self.palette = palette
super.init(nibName: nil, bundle: nil)
}
/**
Sample 2: Once the views are created, we can start receiving `ObjectMonitor` updates in our `ObjectObserver` conformance methods. We typically call this at the end of `viewDidLoad`. Note that after the `addObserver` call, only succeeding updates will trigger our `ObjectObserver` methods, so to immediately display the current values, we need to initialize our views once (in this case, using `reloadPaletteInfo(_:changedKeys:)`.
*/
private func startMonitoringObject() {
self.palette.addObserver(self)
if let palette = self.palette.object {
self.reloadPaletteInfo(palette, changedKeys: nil)
}
}
/**
Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ObjectMonitor`s safely remove deallocated observers automatically.
*/
deinit {
self.palette.removeObserver(self)
}
/**
Sample 4: Our `objectMonitor(_:didUpdateObject:changedPersistentKeys:)` implementation passes a `Set<KeyPathString>` to our reload method. We can then inspect which values were triggered by each `UISlider`, so we can avoid double-updates that can lag the `UISlider` dragging.
*/
func reloadPaletteInfo(
_ palette: Classic.ColorsDemo.Palette,
changedKeys: Set<KeyPathString>?
) {
self.view.backgroundColor = palette.color
self.hueLabel.text = "H: \(Int(palette.hue * 359))°"
self.saturationLabel.text = "S: \(Int(palette.saturation * 100))%"
self.brightnessLabel.text = "B: \(Int(palette.brightness * 100))%"
if changedKeys == nil
|| changedKeys?.contains(String(keyPath: \Classic.ColorsDemo.Palette.hue)) == true {
self.hueSlider.value = Float(palette.hue)
}
if changedKeys == nil
|| changedKeys?.contains(String(keyPath: \Classic.ColorsDemo.Palette.saturation)) == true {
self.saturationSlider.value = palette.saturation
}
if changedKeys == nil
|| changedKeys?.contains(String(keyPath: \Classic.ColorsDemo.Palette.brightness)) == true {
self.brightnessSlider.value = palette.brightness
}
}
// MARK: ObjectObserver
func objectMonitor(
_ monitor: ObjectMonitor<Classic.ColorsDemo.Palette>,
didUpdateObject object: Classic.ColorsDemo.Palette,
changedPersistentKeys: Set<KeyPathString>
) {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view!
let containerView = UIView()
do {
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.backgroundColor = UIColor.white
containerView.layer.cornerRadius = 10
containerView.layer.masksToBounds = true
containerView.layer.shadowColor = UIColor(white: 0.5, alpha: 0.3).cgColor
containerView.layer.shadowOffset = .init(width: 1, height: 1)
containerView.layer.shadowRadius = 2
view.addSubview(containerView)
}
let vStackView = UIStackView()
do {
vStackView.translatesAutoresizingMaskIntoConstraints = false
vStackView.axis = .vertical
vStackView.spacing = 10
vStackView.distribution = .fill
vStackView.alignment = .fill
containerView.addSubview(vStackView)
}
let palette = self.palette.object
let rows: [(label: UILabel, slider: UISlider, initialValue: Float, sliderValueChangedSelector: Selector)] = [
(
self.hueLabel,
self.hueSlider,
palette?.hue ?? 0,
#selector(self.hueSliderValueDidChange(_:))
),
(
self.saturationLabel,
self.saturationSlider,
palette?.saturation ?? 0,
#selector(self.saturationSliderValueDidChange(_:))
),
(
self.brightnessLabel,
self.brightnessSlider,
palette?.brightness ?? 0,
#selector(self.brightnessSliderValueDidChange(_:))
)
]
for (label, slider, initialValue, sliderValueChangedSelector) in rows {
let hStackView = UIStackView()
do {
hStackView.translatesAutoresizingMaskIntoConstraints = false
hStackView.axis = .horizontal
hStackView.spacing = 5
hStackView.distribution = .fill
hStackView.alignment = .center
vStackView.addArrangedSubview(hStackView)
}
do {
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor(white: 0, alpha: 0.8)
label.textAlignment = .center
hStackView.addArrangedSubview(label)
}
do {
slider.translatesAutoresizingMaskIntoConstraints = false
slider.minimumValue = 0
slider.maximumValue = 1
slider.value = initialValue
slider.addTarget(
self,
action: sliderValueChangedSelector,
for: .valueChanged
)
hStackView.addArrangedSubview(slider)
}
}
layout: do {
NSLayoutConstraint.activate(
[
containerView.leadingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 10
),
containerView.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -10
),
containerView.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: -10
),
vStackView.topAnchor.constraint(
equalTo: containerView.topAnchor,
constant: 15
),
vStackView.leadingAnchor.constraint(
equalTo: containerView.leadingAnchor,
constant: 15
),
vStackView.bottomAnchor.constraint(
equalTo: containerView.bottomAnchor,
constant: -15
),
vStackView.trailingAnchor.constraint(
equalTo: containerView.trailingAnchor,
constant: -15
)
]
)
NSLayoutConstraint.activate(
rows.map { label, _, _, _ in
label.widthAnchor.constraint(equalToConstant: 80)
}
)
}
self.startMonitoringObject()
}
// MARK: Private
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError()
}
private let hueLabel: UILabel = .init()
private let saturationLabel: UILabel = .init()
private let brightnessLabel: UILabel = .init()
private let hueSlider: UISlider = .init()
private let saturationSlider: UISlider = .init()
private let brightnessSlider: UISlider = .init()
@objc
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
Classic.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
let palette = transaction.edit(self?.palette.object)
palette?.hue = value
},
completion: { _ in }
)
}
@objc
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
Classic.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
let palette = transaction.edit(self?.palette.object)
palette?.saturation = value
},
completion: { _ in }
)
}
@objc
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
Classic.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
let palette = transaction.edit(self?.palette.object)
palette?.brightness = value
},
completion: { _ in }
)
}
}
}

View File

@@ -0,0 +1,199 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import UIKit
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.ListViewController
final class ListViewController: UITableViewController, ListSectionObserver {
/**
Sample 1: Once the views are created, we can start observing `ListMonitor` updates. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `tableView.reloadData()` once.
*/
private func startObservingList() {
self.listMonitor.addObserver(self)
self.tableView.reloadData()
}
/**
Sample 2: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ListMonitor`s safely remove deallocated observers automatically.
*/
deinit {
self.listMonitor.removeObserver(self)
}
/**
Sample 3: `ListSectionObserver` (and inherently, `ListObjectObserver` and `ListObserver`) conformance
*/
// MARK: ListObserver
typealias ListEntityType = Classic.ColorsDemo.Palette
func listMonitorWillChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
self.tableView.endUpdates()
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
self.tableView.reloadData()
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) {
if case let cell as Classic.ColorsDemo.ItemCell = self.tableView.cellForRow(at: indexPath) {
cell.setPalette(object)
}
}
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
}
// MARK: ListSectionObserver
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
}
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return self.listMonitor.numberOfSections()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listMonitor.numberOfObjects(in: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: Classic.ColorsDemo.ItemCell.reuseIdentifier,
for: indexPath
) as! Classic.ColorsDemo.ItemCell
cell.setPalette(self.listMonitor[indexPath])
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.listMonitor.sectionInfo(at: section).name
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
let object = self.listMonitor[indexPath]
Classic.ColorsDemo.dataStack.perform(
asynchronous: { (transaction) in
transaction.delete(object)
},
completion: { _ in }
)
default:
break
}
}
// MARK: Internal
init(
listMonitor: ListMonitor<Classic.ColorsDemo.Palette>,
onPaletteTapped: @escaping (Classic.ColorsDemo.Palette) -> Void
) {
self.listMonitor = listMonitor
self.onPaletteTapped = onPaletteTapped
super.init(style: .plain)
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(
Classic.ColorsDemo.ItemCell.self,
forCellReuseIdentifier: Classic.ColorsDemo.ItemCell.reuseIdentifier
)
self.startObservingList()
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.onPaletteTapped(
self.listMonitor[indexPath]
)
}
// MARK: Private
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
private let onPaletteTapped: (Classic.ColorsDemo.Palette) -> Void
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError()
}
}
}

View File

@@ -28,8 +28,8 @@ extension Modern.ColorsDemo {
switch self {
case .all: return .init()
case .light: return (\Modern.ColorsDemo.Palette.$brightness >= 0.9)
case .dark: return (\Modern.ColorsDemo.Palette.$brightness <= 0.4)
case .light: return (\.$brightness >= 0.9)
case .dark: return (\.$brightness <= 0.4)
}
}
}

View File

@@ -49,31 +49,31 @@ extension Modern.ColorsDemo {
var brightness: Float
@Field.Virtual(
"colorName",
"colorGroup",
customGetter: { object, field in
if let colorName = field.primitiveValue {
if let colorGroup = field.primitiveValue {
return colorName
return colorGroup
}
let colorName: String
let colorGroup: String
switch object.$hue.value * 359 {
case 0 ..< 20: colorName = "Lower Reds"
case 20 ..< 57: colorName = "Oranges and Browns"
case 57 ..< 90: colorName = "Yellow-Greens"
case 90 ..< 159: colorName = "Greens"
case 159 ..< 197: colorName = "Blue-Greens"
case 197 ..< 241: colorName = "Blues"
case 241 ..< 297: colorName = "Violets"
case 297 ..< 331: colorName = "Magentas"
default: colorName = "Upper Reds"
case 0 ..< 20: colorGroup = "Lower Reds"
case 20 ..< 57: colorGroup = "Oranges and Browns"
case 57 ..< 90: colorGroup = "Yellow-Greens"
case 90 ..< 159: colorGroup = "Greens"
case 159 ..< 197: colorGroup = "Blue-Greens"
case 197 ..< 241: colorGroup = "Blues"
case 241 ..< 297: colorGroup = "Violets"
case 297 ..< 331: colorGroup = "Magentas"
default: colorGroup = "Upper Reds"
}
field.primitiveValue = colorName
return colorName
field.primitiveValue = colorGroup
return colorGroup
}
)
var colorName: String
var colorGroup: String
@Field.Virtual(
"color",
@@ -120,7 +120,7 @@ extension Modern.ColorsDemo {
private static func resetVirtualProperties(_ object: ObjectProxy<Modern.ColorsDemo.Palette>) {
object.$colorName.primitiveValue = nil
object.$colorGroup.primitiveValue = nil
object.$color.primitiveValue = nil
object.$colorText.primitiveValue = nil
}

View File

@@ -45,7 +45,7 @@ extension Modern {
static let palettesPublisher: ListPublisher<Modern.ColorsDemo.Palette> = Modern.ColorsDemo.dataStack.publishList(
From<Modern.ColorsDemo.Palette>()
.sectionBy(\.$colorName)
.sectionBy(\.$colorGroup)
.where(Modern.ColorsDemo.filter.whereClause())
.orderBy(.ascending(\.$hue))
)
@@ -56,7 +56,7 @@ extension Modern {
try! Modern.ColorsDemo.palettesPublisher.refetch(
From<Modern.ColorsDemo.Palette>()
.sectionBy(\.$colorName)
.sectionBy(\.$colorGroup)
.where(self.filter.whereClause())
.orderBy(.ascending(\.$hue))
)

View File

@@ -17,6 +17,9 @@ extension Modern {
// MARK: Internal
/**
Sample 1: Setting up the `DataStack` and storage
*/
static let dataStack: DataStack = {
let dataStack = DataStack(

View File

@@ -9,7 +9,7 @@ import UIKit
extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.Move
// MARK: - Modern.PokedexDemo.PokemonType
/**
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`.

View File

@@ -1914,14 +1914,14 @@ class Vehicle: CoreStoreObject {
Built-in encoders such as `FieldCoders.NSCoding`, `FieldCoders.Json`, and `FieldCoders.Plist` are available, and custom encoding/decoding is also supported:
```swift
class Person: CoreStoreObject {
<br />
struct CustomInfo: Codable {
// ...
}
<br />
@Field.Coded("otherInfo", coder: FieldCoders.Json.self)
var otherInfo: CustomInfo?
<br />
@Field.Coded(
"photo",
coder: {

View File

@@ -65,7 +65,8 @@ extension FieldContainer {
var eyeColor: UIColor = .black
}
```
- parameter initial: the initial value for the property when the object is first created.
- Important: Any changes in the `coder` are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -99,6 +100,25 @@ extension FieldContainer {
)
}
/**
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self, dynamicInitialValue: { UIColor.random() })
var eyeColor: UIColor
}
```
- Important: Any changes in the `coder` are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter coder: The `FieldCoderType` to be used for encoding and decoding the value
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init<Coder: FieldCoderType>(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
@@ -139,7 +159,8 @@ extension FieldContainer {
var bloodType: BloodType = .unknown
}
```
- parameter initial: the initial value for the property when the object is first created.
- Important: Any changes in the encoder/decoder are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -172,7 +193,33 @@ extension FieldContainer {
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
@Field.Coded(
"bloodType",
coder: {
encode: { $0.toData() },
decode: { BloodType(fromData: $0) }
},
dynamicInitialValue: { BloodType.random() }
)
var bloodType: BloodType
}
```
- Important: Any changes in the encoder/decoder are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter coder: The closures to be used for encoding and decoding the value
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
@@ -467,7 +514,8 @@ extension FieldContainer.Coded where V: FieldOptionalType {
var eyeColor: UIColor? = nil
}
```
- parameter initial: the initial value for the property when the object is first created.
- Important: Any changes in the `coder` are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -501,6 +549,25 @@ extension FieldContainer.Coded where V: FieldOptionalType {
)
}
/**
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self, dynamicInitialValue: { UIColor.random() })
var eyeColor: UIColor?
}
```
- Important: Any changes in the `coder` are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter coder: The `FieldCoderType` to be used for encoding and decoding the value
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init<Coder: FieldCoderType>(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
@@ -541,7 +608,8 @@ extension FieldContainer.Coded where V: FieldOptionalType {
var bloodType: BloodType?
}
```
- parameter initial: the initial value for the property when the object is first created.
- Important: Any changes in the encoder/decoder are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -574,7 +642,33 @@ extension FieldContainer.Coded where V: FieldOptionalType {
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
@Field.Coded(
"bloodType",
coder: {
encode: { $0.toData() },
decode: { BloodType(fromData: $0) }
},
dynamicInitialValue: { BloodType.random() }
)
var bloodType: BloodType?
}
```
- Important: Any changes in the encoder/decoder are not reflected in the VersionLock, so make sure that the encoder and decoder logic is compatible for all versions of your persistent store.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter coder: The closures to be used for encoding and decoding the value
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
@@ -615,7 +709,7 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable {
var customInfo: NSDictionary = [:]
}
```
- parameter initial: the initial value for the property when the object is first created.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -647,6 +741,23 @@ extension FieldContainer.Coded where V: DefaultNSSecureCodable {
)
}
/**
Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`.
```
class Person: CoreStoreObject {
@Field.Coded("customInfo", dynamicInitialValue: { ["id": UUID()] })
var customInfo: NSDictionary
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
@@ -686,7 +797,7 @@ extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSS
var customInfo: NSDictionary? = nil
}
```
- parameter initial: the initial value for the property when the object is first created.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -718,6 +829,23 @@ extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSS
)
}
/**
Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`.
```
class Person: CoreStoreObject {
@Field.Coded("customInfo", dynamicInitialValue: { ["id": UUID()] })
var customInfo: NSDictionary?
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,

View File

@@ -59,7 +59,7 @@ extension FieldContainer {
var title: String = "Mr."
}
```
- parameter initial: the initial value for the property when the object is first created.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -90,6 +90,23 @@ extension FieldContainer {
)
}
/**
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
@Field.Stored("title", dynamicInitialValue: { Person.randomTitle() })
var title: String
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
@@ -360,7 +377,7 @@ extension FieldContainer.Stored where V: FieldOptionalType {
var nickname: String?
}
```
- parameter initial: the initial value for the property when the object is first created.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. To assign a value during object creation, use the `dynamicInitialValue` argument instead.
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
@@ -391,6 +408,23 @@ extension FieldContainer.Stored where V: FieldOptionalType {
)
}
/**
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
@Field.Stored("nickname", dynamicInitialValue: { Person.randomNickname() })
var nickname: String?
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
- parameter dynamicInitialValue: the initial value for the property when the object is first created.
*/
public init(
_ keyPath: KeyPathString,
versionHashModifier: @autoclosure @escaping () -> String? = nil,

View File

@@ -74,7 +74,7 @@ extension TransformableContainer {
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)

View File

@@ -77,7 +77,7 @@ extension TransformableContainer {
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)

View File

@@ -71,7 +71,7 @@ extension ValueContainer {
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: the initial value for the property when the object is first created. Defaults to `nil` if not specified.
- parameter initial: the initial value for the property that is shared for all instances of this object. Note that this is evaluated during `DataStack` setup, not during object creation. Defaults to `nil` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.