Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Integrate your app to modern world
Search
d_date
September 21, 2019
Programming
750
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Integrate your app to modern world
iPlayground 2019
Example:
https://github.com/d-date/IntegrateAppExample
d_date
September 21, 2019
More Decks by d_date
See All by d_date
TCA Practice in 5 min
d_date
2
1.9k
waiwai-swiftpm-part2
d_date
3
590
わいわいSwift PM part 1
d_date
2
480
What's new in Firebase 2021
d_date
2
1.6k
CI/CDをミニマルに構築する
d_date
1
630
Swift Package centered project - Build and Practice
d_date
20
17k
How to write Great Proposal
d_date
4
2.3k
Thinking about Architecture for SwiftUI
d_date
8
2.5k
Integrate your app to modern world in Niigata
d_date
0
750
Other Decks in Programming
See All in Programming
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.7k
Performance Engineering for Everyone
elenatanasoiu
0
250
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
260
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
330
Developing with AI Agents — Codex, Claude Code & Cowork Practical Guide
x5gtrn
PRO
0
1.3k
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.6k
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
7.2k
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
230
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
620
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
380
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
3
810
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.7k
Featured
See All Featured
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
Embracing the Ebb and Flow
colly
88
5.1k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
67
56k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
370
Ruling the World: When Life Gets Gamed
codingconduct
0
270
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
Accessibility Awareness
sabderemane
1
150
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
The Cost Of JavaScript in 2023
addyosmani
55
10k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
430
4 Signs Your Business is Dying
shpigford
187
22k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
62
44k
Transcript
Integrate your app to modern world iPlayground 2019 Daiki Matsudate
@d_date iOS Developer
Daiki Matsudate • Tokyo • iOS Developer from iOS 4
• Google Developers Expert for Firebase • Independent Developer • Sushi • Book: ʮiOSΞϓϦઃܭύλʔϯೖʯ
March, 18 - 20th, 2020 https://www.tryswift.co/
Released iOS 13
iPhone 11 Pro
None
None
None
https://twitter.com/ios_memes/status/1174273871983370240?s=21
The Age of Declarative UI
The Age of Declarative UI • iOS: SwiftUI • Android:
Jetpack Compose • ReactNative / Flutter
SwiftUI • UI Framework with declarative Syntax • Using newest
Swift features • Property wrapper • Function Builder • Opaque Result Type
Available in iOS 13
Ready for SwiftUI
Small components
Small Components • Use StackView as possible • Use Xib
as possible • Use UIViewController than UIView
Small Components • Use StackView as possible • Use Xib
as possible • Use UIViewController than UIView super.init(nibName: nil, bundle: nil)
Ex. Compositional Layout • Featured content • Other sections •
The order will be A/B testing
Horizontal CollectionView Vertical CollectionView Ex. Compositional Layout
Vertical Stack view Horizontal CollectionView Vertical CollectionView Ex. Compositional Layout
VStackViewController import UIKit open class VStackViewController: UIViewController { public let
scrollView: UIScrollView = .init() public let stackView: UIStackView = { let stackView: UIStackView = .init() stackView.axis = .vertical stackView.alignment = .fill stackView.distribution = .equalSpacing stackView.spacing = 0 return stackView }() var components: [UIViewController] public init(components: [UIViewController]) { self.components = components super.init(nibName: nil, bundle: nil) }
VStackViewController override open func viewDidLoad() { super.viewDidLoad() components.forEach { [weak
self] in self?.addChild($0) } view.addSubview(scrollView, constraints: .allEdges()) scrollView.addSubview(stackView, constraints: .allEdges() + [equal(\.widthAnchor)]) components.forEach { [weak self] in guard let self = self else { return } self.stackView.addArrangedSubview($0.view) } components.forEach { [weak self] in guard let self = self else { return } $0.didMove(toParent: self) } }
VStackViewController override open func viewDidLoad() { super.viewDidLoad() components.forEach { [weak
self] in self?.addChild($0) } view.addSubview(scrollView, constraints: .allEdges()) scrollView.addSubview(stackView, constraints: .allEdges() + [equal(\.widthAnchor)]) components.forEach { [weak self] in guard let self = self else { return } self.stackView.addArrangedSubview($0.view) } components.forEach { [weak self] in guard let self = self else { return } $0.didMove(toParent: self) } } Set Constraints all edges of view and width anchor
import UIKit final class HomeViewController: VStackViewController { private let featuredComponent
= HomeFeaturedCellViewController(dependencies: .init(onTap: { index in // transition logic })) private let rankingComponents = HomeRankingViewController() init() { super.init(components: [featuredComponent, rankingComponents]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() title = "Home" }
import UIKit final class HomeViewController: VStackViewController { private let featuredComponent
= HomeFeaturedCellViewController(dependencies: .init(onTap: { index in // transition logic })) private let rankingComponents = HomeRankingViewController() init() { super.init(components: [featuredComponent, rankingComponents]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() title = "Home" } Override initializer with child components
Ex. User Registration • Each form need to be validate
• Enable to tap “Done” when all form has valid • Show error below each form
Ex. User Registration Vertical Stack view FormViewController FormViewController FormViewController FormViewController
FormViewController class FormViewController: UIViewController { struct Dependency { let title:
String let placeholder: String let validation: (String) -> ValidationResult let textContentType: UITextContentType? let keyboardType: UIKeyboardType? let isSecureTextEntry: Bool init(title: String, placeholder: String, validation: @escaping (String) -> ValidationResult, textContentType: UITextContentType? = nil, keyboardType: UIKeyboardType? = nil, isSecureTextEntry: Bool = false) { self.title = title self.placeholder = placeholder self.validation = validation self.textContentType = textContentType self.keyboardType = keyboardType self.isSecureTextEntry = isSecureTextEntry } }
FormViewController @IBOutlet private var titleLabel: UILabel! @IBOutlet var textField: UITextField!
@IBOutlet private var errorLabel: UILabel! private let dependency: Dependency private let disposeBag = DisposeBag() private lazy var viewModel: FormViewModel = .init(text: self.textField.rx.text.orEmpty.asDriver(), validation: self.dependency.validation) var isValid: Driver<Bool> { return viewModel.validatedValue.map { $0.isValid } } init(dependency: Dependency) { self.dependency = dependency super.init(nibName: nil, bundle: nil) }
FormViewController override func viewDidLoad() { super.viewDidLoad() errorLabel.isHidden = true titleLabel.text
= dependency.title textField.placeholder = dependency.placeholder textField.textContentType = dependency.textContentType if let keyboardType = dependency.keyboardType { textField.keyboardType = keyboardType } viewModel.validatedValue .drive(errorLabel.rx.validationResult) .disposed(by: disposeBag) }
Stack in horizontal HStack!
HStackViewController import UIKit open class HStackViewController: UIViewController { public let
scrollView: UIScrollView = .init() public let stackView: UIStackView = { let stackView: UIStackView = .init() stackView.axis = .horizontal stackView.alignment = .fill stackView.distribution = .equalSpacing stackView.spacing = 0 return stackView }() var components: [UIViewController] public init(components: [UIViewController]) { self.components = components super.init(nibName: nil, bundle: nil) }
HStackViewController import UIKit open class HStackViewController: UIViewController { public let
scrollView: UIScrollView = .init() public let stackView: UIStackView = { let stackView: UIStackView = .init() stackView.axis = .horizontal stackView.alignment = .fill stackView.distribution = .equalSpacing stackView.spacing = 0 return stackView }() var components: [UIViewController] public init(components: [UIViewController]) { self.components = components super.init(nibName: nil, bundle: nil) } Set to horizontal Scroll View Own child view controllers
HStackViewController override open func viewDidLoad() { super.viewDidLoad() components.forEach { [weak
self] in self?.addChild($0) } view.addSubview(scrollView, constraints: .allEdges()) scrollView.addSubview(stackView, constraints: .allEdges() + [equal(\.heightAnchor)]) components.forEach { [weak self] in guard let self = self else { return } self.stackView.addArrangedSubview($0.view) } components.forEach { [weak self] in guard let self = self else { return } $0.didMove(toParent: self) } }
HStackViewController override open func viewDidLoad() { super.viewDidLoad() components.forEach { [weak
self] in self?.addChild($0) } view.addSubview(scrollView, constraints: .allEdges()) scrollView.addSubview(stackView, constraints: .allEdges() + [equal(\.heightAnchor)]) components.forEach { [weak self] in guard let self = self else { return } self.stackView.addArrangedSubview($0.view) } components.forEach { [weak self] in guard let self = self else { return } $0.didMove(toParent: self) } } Set Constraints all edges of view and height anchor
Gather validation results How to be enabled?
final class RegisterContentViewController: VStackViewController { private let userNameComponent = FormViewController(
dependency: .init( title: "Nickname", placeholder: "Nickname", validation: FormValidationService.shared.validate(nickName:), textContentType: .givenName ) ) private let emailComponent = FormViewController( dependency: .init( title: "Email", placeholder: "Email", validation: FormValidationService.shared.validate(email:), textContentType: .emailAddress ) ) Initialize components
init() { super.init( components: [ userNameComponent, emailComponent, creditCardComponent, HStackViewController( components:
[expirationComponent, securityCodeComponent] ) ] ) } Initialize components
lazy var isValidateForms: Driver<Bool> = Driver.combineLatest( userNameComponent.isValid, emailComponent.isValid, creditCardComponent.isValid, securityCodeComponent.isValid,
expirationComponent.isValid ) { $0 && $1 && $2 && $3 && $4 } .distinctUntilChanged() Passing results isValidateForms .drive(onNext: { [weak self] in guard let self = self else { return } self.confirmViewController.set(isValid: $0) }) .disposed(by: disposeBag)
Gather validation results Enabled
Gather validation results Error Disabled
None
None
final class RegisterContentViewController: VStackViewController { private let userNameComponent = FormViewController(
dependency: .init( title: "Nickname", placeholder: "Nickname", validation: FormValidationService.shared.validate(nickName:), textContentType: .givenName ) ) private let emailComponent = FormViewController( dependency: .init( title: "Email", placeholder: "Email", validation: FormValidationService.shared.validate(email:), textContentType: .emailAddress ) ) Initialize components in UIKit
final class RegisterContentViewController: VStackViewController { private let userNameView = UIHostingController(
rootView: FormView( dependency: .init( title: "Nickname", placeholder: "Nickname", validation: FormValidationService.shared.validate(nickName:), textContentType: .givenName ) )) private let emailView = UIHostingController( rootView: FormView( dependency: .init( title: "Email", placeholder: "Email", validation: FormValidationService.shared.validate(email:), textContentType: .emailAddress ) )) Initialize components in SwiftUI
init() { super.init( components: [ userNameComponent, emailComponent, creditCardComponent, HStackViewController( components:
[expirationComponent, securityCodeComponent] ) ] ) } Initialize components in UIKit
init() { super.init( components: [ userNameView, emailView, creditCardView, HStackViewController( components:
[expirationView, securityCodeView] ) ] ) } Initialize components in SwiftUI
FormViewController.xib
FormViewController.xib VStack Title Text Field Error Label
var body: some View { ZStack { Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack
{ HStack { Text(dependency.title) .font(Font.system(size: 12, weight: .medium, design: .default)) .foregroundColor(Color(UIColor.label)) Spacer() } TextField(dependency.placeholder, text: $viewModel.value) .font(.system(size: 14, weight: .medium, design: .default)) .textFieldStyle(RoundedBorderTextFieldStyle()) .textContentType(dependency.textContentType) .keyboardType(dependency.keyboardType ?? .default) if !viewModel.isValid && !viewModel.isEmpty { HStack { Text(viewModel.errorMessage) .foregroundColor(.red) .font(Font.system(size: 12, weight: .medium, design: .default)) Spacer() } } } .padding() } } FormView in SwiftUI
var body: some View { ZStack { Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack
{ HStack { Text(dependency.title) .font(Font.system(size: 12, weight: .medium, design: .default)) .foregroundColor(Color(UIColor.label)) Spacer() } TextField(dependency.placeholder, text: $viewModel.value) .font(.system(size: 14, weight: .medium, design: .default)) .textFieldStyle(RoundedBorderTextFieldStyle()) .textContentType(dependency.textContentType) .keyboardType(dependency.keyboardType ?? .default) if !viewModel.isValid && !viewModel.isEmpty { HStack { Text(viewModel.errorMessage) .foregroundColor(.red) .font(Font.system(size: 12, weight: .medium, design: .default)) Spacer() } } } .padding() } } FormView in SwiftUI For Light / Dark mode
var body: some View { ZStack { Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack
{ HStack { Text(dependency.title) .font(Font.system(size: 12, weight: .medium, design: .default)) .foregroundColor(Color(UIColor.label)) Spacer() } TextField(dependency.placeholder, text: $viewModel.value) .font(.system(size: 14, weight: .medium, design: .default)) .textFieldStyle(RoundedBorderTextFieldStyle()) .textContentType(dependency.textContentType) .keyboardType(dependency.keyboardType ?? .default) if !viewModel.isValid && !viewModel.isEmpty { HStack { Text(viewModel.errorMessage) .foregroundColor(.red) .font(Font.system(size: 12, weight: .medium, design: .default)) Spacer() } } } .padding() } } FormView in SwiftUI Stack
var body: some View { ZStack { Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack
{ HStack { Text(dependency.title) .font(Font.system(size: 12, weight: .medium, design: .default)) .foregroundColor(Color(UIColor.label)) Spacer() } TextField(dependency.placeholder, text: $viewModel.value) .font(.system(size: 14, weight: .medium, design: .default)) .textFieldStyle(RoundedBorderTextFieldStyle()) .textContentType(dependency.textContentType) .keyboardType(dependency.keyboardType ?? .default) if !viewModel.isValid && !viewModel.isEmpty { HStack { Text(viewModel.errorMessage) .foregroundColor(.red) .font(Font.system(size: 12, weight: .medium, design: .default)) Spacer() } } } .padding() } } FormView in SwiftUI Title Label
var body: some View { ZStack { Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack
{ HStack { Text(dependency.title) .font(Font.system(size: 12, weight: .medium, design: .default)) .foregroundColor(Color(UIColor.label)) Spacer() } TextField(dependency.placeholder, text: $viewModel.value) .font(.system(size: 14, weight: .medium, design: .default)) .textFieldStyle(RoundedBorderTextFieldStyle()) .textContentType(dependency.textContentType) .keyboardType(dependency.keyboardType ?? .default) if !viewModel.isValid && !viewModel.isEmpty { HStack { Text(viewModel.errorMessage) .foregroundColor(.red) .font(Font.system(size: 12, weight: .medium, design: .default)) Spacer() } } } .padding() } } FormView in SwiftUI Text Field
var body: some View { ZStack { Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack
{ HStack { Text(dependency.title) .font(Font.system(size: 12, weight: .medium, design: .default)) .foregroundColor(Color(UIColor.label)) Spacer() } TextField(dependency.placeholder, text: $viewModel.value) .font(.system(size: 14, weight: .medium, design: .default)) .textFieldStyle(RoundedBorderTextFieldStyle()) .textContentType(dependency.textContentType) .keyboardType(dependency.keyboardType ?? .default) if !viewModel.isValid && !viewModel.isEmpty { HStack { Text(viewModel.errorMessage) .foregroundColor(.red) .font(Font.system(size: 12, weight: .medium, design: .default)) Spacer() } } } .padding() } } FormView in SwiftUI Error label
FormView preview struct FormView_Previews: PreviewProvider { static var previews: some
View { let form = FormView(dependency: .init( title: "Email", placeholder: "Email", validation: FormValidationService.shared.validate(email:), textContentType: .emailAddress)) return Group { form.environment(\.colorScheme, .light) form.environment(\.colorScheme, .dark) } .previewLayout(.fixed(width: 414, height: 129)) } }
FormView preview struct FormView_Previews: PreviewProvider { static var previews: some
View { let form = FormView(dependency: .init( title: "Email", placeholder: "Email", validation: FormValidationService.shared.validate(email:), textContentType: .emailAddress)) return Group { form.environment(\.colorScheme, .light) form.environment(\.colorScheme, .dark) } .previewLayout(.fixed(width: 414, height: 129)) } } Light Mode Dark Mode
FormView preview
Dataflow
Dataflow in FormView struct FormView: View { let dependency: FormViewController.Dependency
@ObservedObject var viewModel: FormViewSwiftUIModel init(dependency: FormViewController.Dependency) { self.dependency = dependency self.viewModel = .init(validation: dependency.validation) } var isValid: Bool { viewModel.isValid && !viewModel.isEmpty }
Dataflow in FormView struct FormView: View { let dependency: FormViewController.Dependency
@ObservedObject var viewModel: FormViewSwiftUIModel init(dependency: FormViewController.Dependency) { self.dependency = dependency self.viewModel = .init(validation: dependency.validation) } var isValid: Bool { viewModel.isValid && !viewModel.isEmpty } ViewModel with ObservedObject
https://developer.apple.com/videos/play/wwdc2019/226/
Dataflow in FormView import Foundation import SwiftUI class FormViewSwiftUIModel: ObservableObject
{ let validation: (String) -> ValidationResult var value: String = "" { willSet { if newValue != value { validationResult = self.validation(newValue) } } } var validationResult: ValidationResult = .empty { willSet { objectWillChange.send() } }
Dataflow in FormView var validationResult: ValidationResult = .empty { willSet
{ switch newValue { case .failed(let message): errorMessage = message case .ok: isValid = true isEmpty = false case .empty: isValid = false isEmpty = true default: errorMessage = "" } objectWillChange.send() } }
https://developer.apple.com/videos/play/wwdc2019/226/ Input text $viewModel.value objectWillChange.send()
Demo
Doesn’t work well
UIKit in SwiftUI
UIKit in SwiftUI • Use UIViewControllerRepresentable / UIViewRepresentable • Call
in SwiftUI View
UIKit in SwiftUI extension FormViewController: UIViewControllerRepresentable { typealias UIViewControllerType =
FormViewController func makeUIViewController( context: UIViewControllerRepresentableContext<FormViewController>) -> FormViewController { let formViewController = FormViewController(dependency: self.dependency) return formViewController } func updateUIViewController(_ uiViewController: FormViewController, context: UIViewControllerRepresentableContext<FormViewController>) { } func makeCoordinator() -> () { } }
UIKit in SwiftUI var body: some View { ZStack {
Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack { userNameComponent emailComponent creditCardComponent HStack { expirationComponent securityCodeComponent Spacer() } Spacer() } } }
UIKit in SwiftUI var body: some View { ZStack {
Color(UIColor.systemBackground) .edgesIgnoringSafeArea(.all) VStack { userNameComponent emailComponent creditCardComponent HStack { expirationComponent securityCodeComponent Spacer() } Spacer() } } } super.init( components: [ userNameComponent, emailComponent, creditCardComponent, HStackViewController( components: [expirationComponent, securityCodeComponent] )] )
struct RegisterContentView_Previews: PreviewProvider { static var previews: some View {
let content = RegisterContentView() return Group { NavigationView { content.environment(\.colorScheme, .light) } NavigationView { content.environment(\.colorScheme, .dark) } } } } UIKit in SwiftUI - Preview
Demo
Automatic Preview with UIHostController
Failed to load xib inside SwiftUI
Time to throw away xib
Use Stack view as possible
let stackView = UIStackView(arrangedSubviews: [titleLabel, textField, errorLabel]) stackView.axis = .vertical
stackView.spacing = 16 view.addSubview(stackView, constraints: .allEdges(margin: 16)) Use stack view instead of xib
Use stack view instead of xib
Recap - UIKit to SwiftUI • Use stack view as
possible • Some of SwiftUI feature still be broken • Sometime need to use UIKit instead • Keeping components small is key factor to migrate