Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Aug 19, 2024
1 parent 40534cc commit 0a293a7
Show file tree
Hide file tree
Showing 38 changed files with 2,377 additions and 0 deletions.
450 changes: 450 additions & 0 deletions 0290-cross-platform-pt1/ModernUIKit.xcodeproj/project.pbxproj

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>
15 changes: 15 additions & 0 deletions 0290-cross-platform-pt1/ModernUIKit/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftUI
import UIKitNavigation

@main
struct ModernUIKitApp: App {
var body: some Scene {
WindowGroup {
UIViewControllerRepresenting {
NavigationStackController(
model: AppModel()
)
}
}
}
}
89 changes: 89 additions & 0 deletions 0290-cross-platform-pt1/ModernUIKit/AppFeature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Perception
import SwiftUI
import UIKitNavigation

@Perceptible
class AppModel {
var path: [Path]
init(path: [Path] = []) {
self.path = path
}

enum Path: Hashable {
case counter(CounterModel)
case settings(SettingsModel)
}
}

struct AppView: View {
@Perception.Bindable var model: AppModel

var body: some View {
WithPerceptionTracking {
NavigationStack(path: $model.path) {
Form {
Button("Counter") {
model.path.append(.counter(CounterModel()))
}
Button("Settings") {
model.path.append(.settings(SettingsModel()))
}

NavigationLink("Counter w/ value", value: AppModel.Path.counter(CounterModel()))
}
.navigationDestination(for: AppModel.Path.self) { path in
switch path {
case let .counter(model):
CounterView(model: model)
case let .settings(model):
SettingsView(model: model)
}
}
}
}
}
}

extension NavigationStackController {
convenience init(model: AppModel) {
@UIBindable var model = model
self.init(path: $model.path) {
RootViewController()
}
self.navigationDestination(for: AppModel.Path.self) { path in
switch path {
case let .counter(model):
CounterViewController(model: model)
case let .settings(model):
SettingsViewController(model: model)
}
}
}
}

final class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let counterButton = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in
self?.navigationController?.push(value: AppModel.Path.counter(CounterModel()))
})
counterButton.setTitle("Counter", for: .normal)
let settingsButton = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in
self?.navigationController?.push(value: AppModel.Path.settings(SettingsModel()))
})
settingsButton.setTitle("Settings", for: .normal)
let stack = UIStackView(arrangedSubviews: [
counterButton,
settingsButton,
])
stack.axis = .vertical
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)

NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
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
}
}
232 changes: 232 additions & 0 deletions 0290-cross-platform-pt1/ModernUIKit/CounterFeatureEpoxy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import Epoxy
import UIKit
import UIKitNavigation

class EpoxyCounterViewController: CollectionViewController {
@UIBindable var model: CounterModel

init(model: CounterModel) {
self.model = model
super.init(
layout: UICollectionViewCompositionalLayout.list(
using: UICollectionLayoutListConfiguration(
appearance: .grouped
)
)
)
}

private enum DataID {
case activity
case count
case decrementButton
case fact
case factButton
case incrementButton
}

override func viewDidLoad() {
super.viewDidLoad()

observe { [weak self] in
guard let self else { return }

setItems(items, animated: false)
}

present(item: $model.fact) { fact in
let alert = UIAlertController(
title: "Fact",
message: fact.value,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
return alert
}
}

@ItemModelBuilder
private var items: [ItemModeling] {
Label.itemModel(
dataID: DataID.count,
content: "Count: \(model.count)",
style: .style(with: .title1)
)
ButtonRow.itemModel(
dataID: DataID.decrementButton,
content: ButtonRow.Content(text: "Decrement"),
behaviors: ButtonRow.Behaviors(
didTap: { [weak self] in
self?.model.decrementButtonTapped()
}
)
)
ButtonRow.itemModel(
dataID: DataID.incrementButton,
content: ButtonRow.Content(text: "Increment"),
behaviors: ButtonRow.Behaviors(
didTap: { [weak self] in
self?.model.incrementButtonTapped()
}
)
)
ButtonRow.itemModel(
dataID: DataID.factButton,
content: ButtonRow.Content(text: "Get fact"),
behaviors: ButtonRow.Behaviors(
didTap: { [weak self] in
Task {
await self?.model.factButtonTapped()
}
}
)
)
// if let fact = model.fact {
// Label.itemModel(
// dataID: DataID.fact,
// content: fact.value,
// style: .style(with: .body)
// )
// } else {
ActivityIndicatorView.itemModel(
dataID: DataID.activity,
content: model.factIsLoading,
style: .large
)
// }
}
}

final class ActivityIndicatorView: UIActivityIndicatorView, EpoxyableView {
func setContent(_ content: Bool, animated: Bool) {
if content {
startAnimating()
} else {
stopAnimating()
}
}
}

final class Label: UILabel, @preconcurrency EpoxyableView {

// MARK: Lifecycle

init(style: Style) {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
font = style.font
numberOfLines = style.numberOfLines
if style.showLabelBackground {
backgroundColor = .secondarySystemBackground
}
}

required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

struct Style: Hashable {
let font: UIFont
let showLabelBackground: Bool
var numberOfLines = 0
}

typealias Content = String

func setContent(_ content: String, animated _: Bool) {
text = content
}
}

extension Label.Style {
static func style(
with textStyle: UIFont.TextStyle,
showBackground: Bool = false
) -> Label.Style {
.init(
font: UIFont.preferredFont(forTextStyle: textStyle),
showLabelBackground: showBackground
)
}
}

final class ButtonRow: UIView, @preconcurrency EpoxyableView {

// MARK: Lifecycle

init() {
super.init(frame: .zero)
setUp()
}

required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: Internal

struct Behaviors {
var didTap: (() -> Void)?
}

struct Content: Equatable {
var text: String?
}

func setContent(_ content: Content, animated _: Bool) {
text = content.text
}

func setBehaviors(_ behaviors: Behaviors?) {
didTap = behaviors?.didTap
}

// MARK: Private

private let button = UIButton(type: .system)
private var didTap: (() -> Void)?

private var text: String? {
get { button.title(for: .normal) }
set { button.setTitle(newValue, for: .normal) }
}

private func setUp() {
translatesAutoresizingMaskIntoConstraints = false
layoutMargins = UIEdgeInsets(top: 20, left: 24, bottom: 20, right: 24)
backgroundColor = .quaternarySystemFill

button.tintColor = .systemBlue
button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .title3)
button.translatesAutoresizingMaskIntoConstraints = false

addSubview(button)
NSLayoutConstraint.activate([
button.leadingAnchor
.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
button.topAnchor
.constraint(equalTo: layoutMarginsGuide.topAnchor),
button.trailingAnchor
.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
button.bottomAnchor
.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
])

button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
}

@objc
private func handleTap() {
didTap?()
}
}

import SwiftUI
// #Preview
struct EpoxyCounterViewControllerPreview: PreviewProvider {
static var previews: some View {
UIViewControllerRepresenting {
EpoxyCounterViewController(model: CounterModel())
}
}
}
Loading

0 comments on commit 0a293a7

Please sign in to comment.