Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Jun 24, 2024
1 parent 06049b2 commit 1534d41
Show file tree
Hide file tree
Showing 14 changed files with 805 additions and 1 deletion.

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
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
}
}
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 0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/CounterFeature.swift
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) {
}
}
11 changes: 11 additions & 0 deletions 0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/Info.plist
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 0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/ModernUIKitApp.swift
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 0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/Navigation.swift
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 0284-modern-uikit-pt4/ModernUIKit/ModernUIKit/Observation.swift
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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading

0 comments on commit 1534d41

Please sign in to comment.