Open20
SwiftUI
Tutrial
Show SwiftUI's View in UIViewController
// in UIViewController
let childView = UIHostingController(rootView: SwiftUIView())
addChild(childView)
childView.view.frame = view.frame
view.addSubview(childView.view)
childView.didMove(toParent: self)
Example
Document-Based app
App essentials in SwiftUI
- Viewに加え、Scene・Appプロトコルが追加された。
- この追加により、SwiftUIのみでアプリ全体を構築することができるようになった。
- iPadOS・macOSではマルチウインドウが可能で、後ほど説明するように、Scene・WindowGroupによりSwiftUIでマルチウインドウの制御が可能になる。 - SceneはViewやSceneから構成される。
- Sceneはウインドウ・複数のScene・複数のViewを内包しうる。
- macOSのタブUIはそれぞれのタブに対応するSceneがあり、複数のタブを束ねるウインドウはSceneの配下にある。
- Sceneはウインドウ・複数のScene・複数のViewを内包しうる。
サンプルコード
App・Scene・WindowGroup・Viewの関係はSwiftUIのコード上では以下のようになる:
@main
struct BookClubApp: App {
@StateObject private var store = ReadingListStore()
var body: some Scene {
WindowGroup {
ReadingListViewer(store: store)
}
}
}
ここまでの説明に出てきた図と対応することが容易にわかる。
Viewは以下のようになる:
struct ReadingListViewer: View {
@ObservedObject var store: ReadingListStore
var body: some View {
NavigationView {
List(store.books) { book in
Text(book.title)
}
.navigationTitle("Currently Reading")
}
}
}
モデルオブジェクトは以下のようになる:
class ReadingListStore: ObservableObject {
init() {}
var books = [
Book(title: "Book #1", author: "Author #1"),
Book(title: "Book #2", author: "Author #2"),
Book(title: "Book #3", author: "Author #3")
]
}
struct Book: Identifiable {
let id = UUID()
let title: String
let author: String
}
WindowGroup
iPadOSのApp Switcherで単一のアプリの新しいウインドウを作成する機能は、WindowGroupによって提供されている。
- それぞれのウインドウは独立した状態を持つ
- 一方のウインドウでの選択状態は他方のそれに影響を与えない
- Navigation Titleも同様
- 複数のウインドウは同一のデータを持つ
- 共通のモデルデータをそれぞれのSceneに提供している
上記の特徴は、macOSで複数のウインドウを生成した際も同様の挙動になる。
- さらに、Navigation TitleはWindowメニューにも現れる。それぞれのNavigation Titleを選択することで、ウインドウを最前面に表示する
- また、複数のウインドウを単一のウインドウ内にタブの集まりとしてまとめることもできる。
- この場合、それぞれのタブが独立したSceneで表現されることになる。
複数のウインドウが許容されるプラットフォームでは、WindowGroupが複数のSceneを生成し複数のウインドウを表示する。それぞれのSceneが単一のデータ源をもとに独立した状態を持っている。
その他
- Document-Based AppをSwiftUIで実現するために、DocumentGroup Sceneが導入された
- こちらは、Build Document-Based Apps in SwiftUIを参照のこと
- macOSの設定画面をサポートするSettings Scene Typeが導入された。
- アプリメニューに設定項目を追加し、設定画面をのウインドウをサポートする
- コンテキストメニューを提供するCommandsも追加された
Readable Content Widthのコンセプトを持ち込む
AsyncImage
iOS15以降
ProgressViewStyle
他の宣言型UIフレームワークとの比較
SwiftUI in Swift Playgrounds
次のように書くことで、Xcode Playground(ここでは、Swift Playgroundと区別するためにこのように呼びます)でSwiftUIのViewを扱うことができます。
import UIKit
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello world!")
}
}
let host = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = host
特定の位置(ビュー)までスクロールしたい
ScrollViewReaderを使う。
ScrollViewReader { proxy in
// onChange と @Publishedなプロパティの組み合わせでも可
View.onReceive(someAnyPublisher) { index in
if let index {
withAnimation { proxy.scrollTo(index) }
}
}
}
PassthroughSubject
をAnyPublisherに変換することもできる:
private var aaa = PassthroughSubject<Int, Never>()
var somePublisher: AnyPublisher<Int, Never> {
aaa.eraseToAnyPublisher()
}
プレビュー
ローカリゼーション
NSLocalizableStringがXcode 14.1の Previewだとうまく機能しない😢
端末の向き
iOS15以降は、previewInterfaceOrientation
で指定できる。
iOS14のシミュレータベースだと、うまく機能しない😢
参考
Xcode15以降のPreview
Activity Indicator
GeometryReader
Custom layouts
余白の設定
Grid
LazyGrid
GridItem
ウインドウの幅に合わせて、表示する行 or 列の数を変えたい時は、GridItem(.adaptive(minimum:))
を使う
最低限の幅をminimum引数で指定し、行数 or 列数は ウインドウ幅によって決定される。
fixed, flexible, adaptive
Text内のリンクを検出してタップしたらリンク先を表示する
swiftui fit image in frame keeping aspect ratio
Image("example_image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200, height: 200)