-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
06049b2
commit 1534d41
Showing
14 changed files
with
805 additions
and
1 deletion.
There are no files selected for viewing
413 changes: 413 additions & 0 deletions
413
0284-modern-uikit-pt4/ModernUIKit/ModernUIKit.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
...-uikit-pt4/ModernUIKit/ModernUIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
...dernUIKit/ModernUIKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
11 changes: 11 additions & 0 deletions
11
...dern-uikit-pt4/ModernUIKit/ModernUIKit/Assets.xcassets/AccentColor.colorset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"colors" : [ | ||
{ | ||
"idiom" : "universal" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...modern-uikit-pt4/ModernUIKit/ModernUIKit/Assets.xcassets/AppIcon.appiconset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"idiom" : "universal", | ||
"platform" : "ios", | ||
"size" : "1024x1024" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
217 changes: 217 additions & 0 deletions
217
0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/CounterFeature.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
import Perception | ||
@preconcurrency import SwiftUI | ||
import SwiftUINavigation | ||
|
||
@MainActor | ||
@Perceptible | ||
class CounterModel { | ||
var count = 0 | ||
var fact: Fact? | ||
var factIsLoading = false | ||
struct Fact: Identifiable { | ||
let value: String | ||
var id: String { value } | ||
} | ||
func incrementButtonTapped() { | ||
count += 1 | ||
fact = nil | ||
} | ||
func decrementButtonTapped() { | ||
count -= 1 | ||
fact = nil | ||
} | ||
func factButtonTapped() async { | ||
withUIAnimation { | ||
self.fact = nil | ||
} | ||
self.factIsLoading = true | ||
defer { self.factIsLoading = false } | ||
|
||
do { | ||
try await Task.sleep(for: .seconds(1)) | ||
let loadedFact = try await String( | ||
decoding: URLSession.shared | ||
.data( | ||
from: URL(string: "http://www.numberapi.com/\(count)")! | ||
).0, | ||
as: UTF8.self | ||
) | ||
withUIAnimation { | ||
self.fact = Fact(value: loadedFact) | ||
} | ||
} catch { | ||
// TODO: error handling | ||
} | ||
try? await Task.sleep(for: .seconds(1)) | ||
count += 1 | ||
try? await Task.sleep(for: .seconds(2)) | ||
fact = nil | ||
} | ||
} | ||
|
||
struct CounterView: View { | ||
@Perception.Bindable var model: CounterModel | ||
var body: some View { | ||
WithPerceptionTracking { | ||
Form { | ||
Text("\(model.count)") | ||
Button("Decrement") { model.decrementButtonTapped() } | ||
Button("Increment") { model.incrementButtonTapped() } | ||
|
||
if model.factIsLoading { | ||
ProgressView().id(UUID()) | ||
} | ||
|
||
Button("Get fact") { | ||
Task { | ||
await model.factButtonTapped() | ||
} | ||
} | ||
} | ||
.disabled(model.factIsLoading) | ||
.sheet(item: $model.fact) { fact in | ||
Text(fact.value) | ||
} | ||
// .alert(item: $model.fact) { _ in | ||
// Text(model.count.description) | ||
// } actions: { _ in | ||
// } message: { fact in | ||
// Text(fact) | ||
// } | ||
} | ||
} | ||
} | ||
|
||
#Preview("SwiftUI") { | ||
CounterView(model: CounterModel()) | ||
} | ||
|
||
final class CounterViewController: UIViewController { | ||
let model: CounterModel | ||
|
||
init(model: CounterModel) { | ||
self.model = model | ||
super.init(nibName: nil, bundle: nil) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
let countLabel = UILabel() | ||
countLabel.textAlignment = .center | ||
let decrementButton = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in | ||
self?.model.decrementButtonTapped() | ||
}) | ||
decrementButton.setTitle("Decrement", for: .normal) | ||
let incrementButton = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in | ||
self?.model.incrementButtonTapped() | ||
}) | ||
incrementButton.setTitle("Increment", for: .normal) | ||
|
||
let factLabel = UILabel() | ||
factLabel.numberOfLines = 0 | ||
let activityIndicator = UIActivityIndicatorView() | ||
activityIndicator.startAnimating() | ||
let factButton = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in | ||
guard let self else { return } | ||
Task { await self.model.factButtonTapped() } | ||
}) | ||
factButton.setTitle("Get fact", for: .normal) | ||
|
||
let counterStack = UIStackView(arrangedSubviews: [ | ||
countLabel, | ||
decrementButton, | ||
incrementButton, | ||
factLabel, | ||
activityIndicator, | ||
factButton, | ||
]) | ||
counterStack.axis = .vertical | ||
counterStack.spacing = 12 | ||
counterStack.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
view.addSubview(counterStack) | ||
NSLayoutConstraint.activate([ | ||
counterStack.centerXAnchor.constraint(equalTo: view.centerXAnchor), | ||
counterStack.centerYAnchor.constraint(equalTo: view.centerYAnchor), | ||
counterStack.leadingAnchor.constraint(equalTo: view.leadingAnchor), | ||
counterStack.trailingAnchor.constraint(equalTo: view.trailingAnchor), | ||
]) | ||
|
||
//var alertController: UIAlertController? | ||
weak var factController: UIViewController? | ||
observe { [weak self] in | ||
guard let self else { return } | ||
countLabel.text = "\(model.count)" | ||
|
||
activityIndicator.isHidden = !model.factIsLoading | ||
// factLabel.text = model.fact | ||
// factLabel.isHidden = model.fact == nil | ||
decrementButton.isEnabled = !model.factIsLoading | ||
incrementButton.isEnabled = !model.factIsLoading | ||
factButton.isEnabled = !model.factIsLoading | ||
|
||
// navigationController?.pushViewController(item: model.fact) { fact in | ||
// FactViewController(fact: fact) | ||
// } | ||
// | ||
// .sheet(item: $model.fact) { fact in | ||
// FactView(fact: fact.fact) | ||
// } | ||
|
||
present(item: model.fact) { fact in | ||
FactViewController(fact: fact.value) | ||
} | ||
} | ||
} | ||
} | ||
|
||
class FactViewController: UIViewController { | ||
let fact: String | ||
init(fact: String) { | ||
self.fact = fact | ||
super.init(nibName: nil, bundle: nil) | ||
} | ||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
view.backgroundColor = .white | ||
let factLabel = UILabel() | ||
factLabel.text = fact | ||
factLabel.numberOfLines = 0 | ||
factLabel.translatesAutoresizingMaskIntoConstraints = false | ||
view.addSubview(factLabel) | ||
NSLayoutConstraint.activate([ | ||
factLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), | ||
factLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), | ||
factLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), | ||
factLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), | ||
]) | ||
} | ||
} | ||
|
||
#Preview("UIKit") { | ||
UIViewControllerRepresenting { | ||
CounterViewController(model: CounterModel()) | ||
} | ||
} | ||
|
||
struct UIViewControllerRepresenting: UIViewControllerRepresentable { | ||
let base: UIViewController | ||
init(base: () -> UIViewController) { | ||
self.base = base() | ||
} | ||
|
||
func makeUIViewController(context: Context) -> some UIViewController { | ||
return base | ||
} | ||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>NSAppTransportSecurity</key> | ||
<dict> | ||
<key>NSAllowsArbitraryLoads</key> | ||
<true/> | ||
</dict> | ||
</dict> | ||
</plist> |
17 changes: 17 additions & 0 deletions
17
0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/ModernUIKitApp.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import SwiftUI | ||
|
||
@main | ||
struct ModernUIKitApp: App { | ||
var body: some Scene { | ||
WindowGroup { | ||
// CounterView(model: CounterModel()) | ||
UIViewControllerRepresenting { | ||
UINavigationController( | ||
rootViewController: CounterViewController( | ||
model: CounterModel() | ||
) | ||
) | ||
} | ||
} | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/Navigation.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import ObjectiveC | ||
import UIKit | ||
|
||
extension UIViewController { | ||
private var presented: UIViewController? { | ||
get { | ||
objc_getAssociatedObject( | ||
self, | ||
presentedKey | ||
) as? UIViewController | ||
} | ||
set { | ||
objc_setAssociatedObject( | ||
self, | ||
presentedKey, | ||
newValue, | ||
.OBJC_ASSOCIATION_RETAIN_NONATOMIC | ||
) | ||
} | ||
} | ||
|
||
func present<Item>( | ||
item: Item?, | ||
content: (Item) -> UIViewController | ||
) { | ||
if let item, presented == nil { | ||
let controller = content(item) | ||
presented = controller | ||
present(controller, animated: true) | ||
} else if item == nil, let controller = presented { | ||
controller.dismiss(animated: true) | ||
presented = nil | ||
} | ||
} | ||
} | ||
|
||
private let presentedKey = malloc(1)! |
52 changes: 52 additions & 0 deletions
52
0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/Observation.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import Foundation | ||
import Perception | ||
import UIKit | ||
|
||
@MainActor | ||
func observe( | ||
apply: @escaping @MainActor @Sendable () -> Void | ||
) { | ||
onChange(apply: apply) | ||
} | ||
@MainActor | ||
func onChange( | ||
apply: @escaping @MainActor @Sendable () -> Void | ||
) { | ||
withPerceptionTracking { | ||
apply() | ||
} onChange: { | ||
Task { @MainActor in | ||
if let animation = UIAnimation.current { | ||
UIView.animate(withDuration: animation.duration) { | ||
onChange(apply: apply) | ||
} | ||
} else { | ||
onChange(apply: apply) | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension NSObject { | ||
@MainActor | ||
func observe( | ||
apply: @escaping @MainActor @Sendable () -> Void | ||
) { | ||
ModernUIKit.observe(apply: apply) | ||
} | ||
} | ||
|
||
struct UIAnimation { | ||
@TaskLocal fileprivate static var current: Self? | ||
var duration: TimeInterval | ||
} | ||
|
||
@MainActor | ||
func withUIAnimation( | ||
_ animation: UIAnimation? = UIAnimation(duration: 0.3), | ||
body: @escaping () -> Void | ||
) { | ||
UIAnimation.$current.withValue(animation) { | ||
body() | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...n-uikit-pt4/ModernUIKit/ModernUIKit/Preview Content/Preview Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
Oops, something went wrong.