Xcode 14, iOS 16 SDK (min iOS 13)

This commit is contained in:
John Estropia
2022-06-19 17:56:42 +09:00
parent 3317867a2f
commit d1f83badef
121 changed files with 217 additions and 235 deletions

View File

@@ -0,0 +1,10 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
// MARK: - Classic
/**
Sample usages for `NSManagedObject` subclasses
*/
enum Classic {}

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

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,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

@@ -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.1 ... 0.9)
}
}

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,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20C5048l" 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"/>
<fetchedProperty name="testFetchProperty" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="Palette"/>
</fetchedProperty>
</entity>
<elements>
<element name="Palette" positionX="-63" positionY="-18" width="128" height="110"/>
</elements>
</model>