Prototyping SwiftUI utilities

This commit is contained in:
John Estropia
2021-02-16 09:12:36 +09:00
parent edd8ba55d8
commit f7471f56a4
27 changed files with 853 additions and 227 deletions

View File

@@ -13,12 +13,6 @@ extension Modern.ColorsDemo {
struct MainView<ListView: View, DetailView: View>: View {
/**
Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
*/
@ObservedObject
private var listPublisher: ListPublisher<Modern.ColorsDemo.Palette>
// MARK: Internal
init(
@@ -30,38 +24,24 @@ extension Modern.ColorsDemo {
self.listView = listView
self.detailView = detailView
self.listPublisher = Modern.ColorsDemo.palettesPublisher
self._filter = Binding(
get: { Modern.ColorsDemo.filter },
set: { Modern.ColorsDemo.filter = $0 }
)
}
// MARK: View
var body: some View {
let detailView: AnyView
if let selectedPalette = self.selectedPalette {
detailView = AnyView(
self.detailView(selectedPalette)
)
}
else {
detailView = AnyView(EmptyView())
}
let listPublisher = self.listPublisher
return VStack(spacing: 0) {
self.listView(listPublisher, { self.selectedPalette = $0 })
self.listView(self.$palettes, { self.selectedPalette = $0 })
.navigationBarTitle(
Text("Colors (\(listPublisher.snapshot.numberOfItems) objects)")
Text("Colors (\(self.palettes.count) objects)")
)
.frame(minHeight: 0, maxHeight: .infinity)
detailView
.edgesIgnoringSafeArea(.all)
.frame(minHeight: 0, maxHeight: .infinity)
self.selectedPalette.map {
self.detailView($0)
.edgesIgnoringSafeArea(.all)
.frame(minHeight: 0, maxHeight: .infinity)
}
}
.navigationBarItems(
leading: HStack {
@@ -91,6 +71,9 @@ extension Modern.ColorsDemo {
// MARK: Private
@LiveList(Modern.ColorsDemo.palettesPublisher)
private var palettes: LiveList<Modern.ColorsDemo.Palette>.Items
private let listView: (
_ listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
_ onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
@@ -103,12 +86,13 @@ extension Modern.ColorsDemo {
@State
private var selectedPalette: ObjectPublisher<Modern.ColorsDemo.Palette>?
@Binding
private var filter: Modern.ColorsDemo.Filter
@State
private var filter: Modern.ColorsDemo.Filter = Modern.ColorsDemo.filter
private func changeFilter() {
Modern.ColorsDemo.filter = Modern.ColorsDemo.filter.next()
self.filter = Modern.ColorsDemo.filter
}
private func clearColors() {

View File

@@ -2,6 +2,7 @@
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
import CoreStore
// MARK: - Modern

View File

@@ -17,8 +17,8 @@ extension Modern.ColorsDemo.SwiftUI {
/**
Sample 1: Setting an `ObjectPublisher` declared as an `@ObservedObject`
*/
@ObservedObject
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
@LiveObject
private var palette: LiveObject<Modern.ColorsDemo.Palette>.Item?
/**
Sample 2: Setting properties that can be binded to controls (`Slider` in this case) by creating custom `@Binding` instances that updates the store when the values change.
@@ -34,7 +34,7 @@ extension Modern.ColorsDemo.SwiftUI {
init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
self.palette = palette
self._palette = .init(palette)
self._hue = Binding(
get: { palette.hue ?? 0 },
set: { percentage in
@@ -84,56 +84,50 @@ extension Modern.ColorsDemo.SwiftUI {
var body: some View {
guard let snapshot = self.palette.snapshot else {
if let palette = self.palette {
return AnyView(EmptyView())
}
return AnyView(
GeometryReader { geometry in
ZStack(alignment: .bottom) {
Color(snapshot.$color)
ZStack {
Color.white
.cornerRadius(10)
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("H: \(Int(snapshot.$hue * 359))°")
.frame(width: 80)
Slider(
value: self.$hue,
in: 0 ... 1,
step: 1 / 359
)
}
HStack {
Text("S: \(Int(snapshot.$saturation * 100))%")
.frame(width: 80)
Slider(
value: self.$saturation,
in: 0 ... 1,
step: 1 / 100
)
}
HStack {
Text("B: \(Int(snapshot.$brightness * 100))%")
.frame(width: 80)
Slider(
value: self.$brightness,
in: 0 ... 1,
step: 1 / 100
)
}
ZStack(alignment: .center) {
Color(palette.$color)
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.white)
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("H: \(Int(palette.$hue * 359))°")
.frame(width: 80)
Slider(
value: self.$hue,
in: 0 ... 1,
step: 1 / 359
)
}
HStack {
Text("S: \(Int(palette.$saturation * 100))%")
.frame(width: 80)
Slider(
value: self.$saturation,
in: 0 ... 1,
step: 1 / 100
)
}
HStack {
Text("B: \(Int(palette.$brightness * 100))%")
.frame(width: 80)
Slider(
value: self.$brightness,
in: 0 ... 1,
step: 1 / 100
)
}
.foregroundColor(Color(.sRGB, white: 0, opacity: 0.8))
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(Color(.sRGB, white: 0, opacity: 0.8))
.padding()
.padding(geometry.safeAreaInsets)
}
.fixedSize(horizontal: false, vertical: true)
.padding()
}
)
}
}
}
}

View File

@@ -16,15 +16,15 @@ extension Modern.ColorsDemo.SwiftUI {
/**
Sample 1: Setting an `ObjectPublisher` declared as an `@ObservedObject`
*/
@ObservedObject
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
@LiveObject
private var palette: LiveObject<Modern.ColorsDemo.Palette>.Item?
// MARK: Internal
internal init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
self.palette = palette
self._palette = .init(palette)
}
@@ -32,18 +32,16 @@ extension Modern.ColorsDemo.SwiftUI {
var body: some View {
guard let palette = self.palette.snapshot else {
if let palette = self.palette {
return AnyView(EmptyView())
}
return AnyView(
Color(palette.$color).overlay(
Text(palette.$colorText)
.foregroundColor(palette.$brightness > 0.6 ? .black : .white)
.padding(),
alignment: .leading
)
)
.animation(.default)
}
}
}
}

View File

@@ -5,7 +5,6 @@
import CoreStore
import SwiftUI
// MARK: - Modern.ColorsDemo.SwiftUI
extension Modern.ColorsDemo.SwiftUI {
@@ -17,39 +16,43 @@ extension Modern.ColorsDemo.SwiftUI {
/**
Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
*/
@ObservedObject
private var listPublisher: ListPublisher<Modern.ColorsDemo.Palette>
@LiveList
private var palettes: LiveList<Modern.ColorsDemo.Palette>.Items
/**
Sample 2: Assigning sections and items of the `ListPublisher` to corresponding `View`s
*/
var body: some View {
let listSnapshot = self.listPublisher.snapshot
return List {
ForEach(listSnapshot.sectionIDs, id: \.self) { (sectionID) in
ForEachSection(in: self.palettes) { sectionID, palettes in
Section(header: Text(sectionID)) {
ForEach(listSnapshot.items(inSectionWithID: sectionID), id: \.self) { palette in
ForEach(palettes) { palette in
Button(
action: {
self.onPaletteTapped(palette)
},
label: {
Modern.ColorsDemo.SwiftUI.ItemView(palette)
}
)
.listRowInsets(.init())
}
.onDelete { itemIndices in
self.deleteColors(at: itemIndices, in: sectionID)
}
}
}
GeometryReader { geometry in
Spacer(minLength: geometry.safeAreaInsets.bottom)
}
}
.animation(.default)
.listStyle(PlainListStyle())
.edgesIgnoringSafeArea([])
}
@@ -60,7 +63,7 @@ extension Modern.ColorsDemo.SwiftUI {
onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
) {
self.listPublisher = listPublisher
self._palettes = .init(listPublisher)
self.onPaletteTapped = onPaletteTapped
}
@@ -71,7 +74,7 @@ extension Modern.ColorsDemo.SwiftUI {
private func deleteColors(at indices: IndexSet, in sectionID: String) {
let objectIDsToDelete = self.listPublisher.snapshot.itemIDs(
let objectIDsToDelete = self.palettes.itemIDs(
inSectionWithID: sectionID,
atIndices: indices
)

View File

@@ -36,17 +36,13 @@ extension Modern.ColorsDemo.UIKit {
*/
private func startObservingList() {
let dataSource = self.dataSource
self.listPublisher.addObserver(self) { (listPublisher) in
self.dataSource.apply(
listPublisher.snapshot,
animatingDifferences: true
)
dataSource.apply(listPublisher.snapshot, animatingDifferences: true)
}
self.dataSource.apply(
listPublisher.snapshot,
animatingDifferences: false
)
dataSource.apply(self.listPublisher.snapshot, animatingDifferences: false)
}
/**

View File

@@ -12,30 +12,28 @@ extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.MainView
struct MainView: View {
struct MainView<ListView: View>: View {
// MARK: Internal
init() {
self.pokedexEntries = Modern.PokedexDemo.pokedexEntries
init(
listView: @escaping () -> ListView
) {
self.listView = listView
}
// MARK: View
var body: some View {
let pokedexEntries = self.pokedexEntries.snapshot
return ZStack {
ZStack {
Modern.PokedexDemo.ListView(
service: self.service,
listPublisher: self.pokedexEntries
)
self.listView()
.frame(minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.vertical)
if pokedexEntries.isEmpty {
if self.pokedexEntries.isEmpty {
VStack(alignment: .center, spacing: 30) {
Text("This demo needs to make a network connection to download Pokedex entries")
@@ -64,11 +62,17 @@ extension Modern.PokedexDemo {
// MARK: Private
@ObservedObject
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
@LiveList(
From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$index)),
in: Modern.PokedexDemo.dataStack
)
private var pokedexEntries
@ObservedObject
private var service: Modern.PokedexDemo.Service = .init()
private let listView: () -> ListView
}
}
@@ -82,7 +86,9 @@ struct _Demo_Modern_PokedexDemo_MainView_Preview: PreviewProvider {
static var previews: some View {
Modern.PokedexDemo.MainView()
Modern.PokedexDemo.MainView(
listView: Modern.PokedexDemo.UIKit.ListView.init
)
}
}

View File

@@ -7,9 +7,9 @@ import CoreStore
import UIKit
// MARK: - Modern.PokedexDemo
// MARK: - Modern.PokedexDemo.UIKit
extension Modern.PokedexDemo {
extension Modern.PokedexDemo.UIKit {
// MARK: - Modern.PokedexDemo.ItemCell
@@ -17,7 +17,7 @@ extension Modern.PokedexDemo {
// MARK: Internal
static let reuseIdentifier: String = NSStringFromClass(Modern.PokedexDemo.ItemCell.self)
static let reuseIdentifier: String = NSStringFromClass(Modern.PokedexDemo.UIKit.ItemCell.self)
func setPokedexEntry(
_ pokedexEntry: Modern.PokedexDemo.PokedexEntry,
@@ -61,6 +61,7 @@ extension Modern.PokedexDemo {
contentView.backgroundColor = UIColor.placeholderText.withAlphaComponent(0.1)
contentView.layer.cornerRadius = 10
contentView.layer.cornerCurve = .continuous
contentView.layer.masksToBounds = true
}

View File

@@ -5,9 +5,9 @@
import CoreStore
import SwiftUI
// MARK: - Modern.PokedexDemo
// MARK: - Modern.PokedexDemo.UIKit
extension Modern.PokedexDemo {
extension Modern.PokedexDemo.UIKit {
// MARK: - Modern.PokedexDemo.ListView
@@ -15,19 +15,20 @@ extension Modern.PokedexDemo {
// MARK: Internal
init(
service: Modern.PokedexDemo.Service,
listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
) {
init() {
self.service = service
self.listPublisher = listPublisher
self.service = Modern.PokedexDemo.Service.init()
self.listPublisher = Modern.PokedexDemo.dataStack
.publishList(
From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$index))
)
}
// MARK: UIViewControllerRepresentable
typealias UIViewControllerType = Modern.PokedexDemo.ListViewController
typealias UIViewControllerType = Modern.PokedexDemo.UIKit.ListViewController
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
@@ -53,7 +54,7 @@ extension Modern.PokedexDemo {
#if DEBUG
struct _Demo_Modern_PokedexDemo_ListView_Preview: PreviewProvider {
struct _Demo_Modern_PokedexDemo_UIKit_ListView_Preview: PreviewProvider {
// MARK: PreviewProvider
@@ -62,10 +63,7 @@ struct _Demo_Modern_PokedexDemo_ListView_Preview: PreviewProvider {
let service = Modern.PokedexDemo.Service()
service.fetchPokedexEntries()
return Modern.PokedexDemo.ListView(
service: service,
listPublisher: Modern.PokedexDemo.pokedexEntries
)
return Modern.PokedexDemo.UIKit.ListView()
}
}

View File

@@ -6,9 +6,9 @@ import CoreStore
import UIKit
// MARK: - Modern.PokedexDemo
// MARK: - Modern.PokedexDemo.UIKit
extension Modern.PokedexDemo {
extension Modern.PokedexDemo.UIKit {
// MARK: - Modern.PokedexDemo.ListViewController
@@ -65,8 +65,8 @@ extension Modern.PokedexDemo {
self.collectionView.backgroundColor = UIColor.systemBackground
self.collectionView.register(
Modern.PokedexDemo.ItemCell.self,
forCellWithReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier
Modern.PokedexDemo.UIKit.ItemCell.self,
forCellWithReuseIdentifier: Modern.PokedexDemo.UIKit.ItemCell.reuseIdentifier
)
self.startObservingList()
@@ -84,9 +84,9 @@ extension Modern.PokedexDemo {
cellProvider: { (collectionView, indexPath, pokedexEntry) in
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier,
withReuseIdentifier: Modern.PokedexDemo.UIKit.ItemCell.reuseIdentifier,
for: indexPath
) as! Modern.PokedexDemo.ItemCell
) as! Modern.PokedexDemo.UIKit.ItemCell
cell.setPokedexEntry(pokedexEntry, service: self.service)
return cell
}

View File

@@ -0,0 +1,13 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
// MARK: - Modern.PokedexDemo
extension Modern.PokedexDemo {
// MARK: - UIKit
enum UIKit {}
}