Open20

SwiftUI

saharasahara

App essentials in SwiftUI

https://developer.apple.com/videos/play/wwdc2020/10037/

  • Viewに加え、Scene・Appプロトコルが追加された。
  • この追加により、SwiftUIのみでアプリ全体を構築することができるようになった。
     - iPadOS・macOSではマルチウインドウが可能で、後ほど説明するように、Scene・WindowGroupによりSwiftUIでマルチウインドウの制御が可能になる。
  • SceneはViewやSceneから構成される。
    • Sceneはウインドウ・複数のScene・複数のViewを内包しうる。
      • macOSのタブUIはそれぞれのタブに対応するSceneがあり、複数のタブを束ねるウインドウはSceneの配下にある。

objects hirarchy

サンプルコード

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が単一のデータ源をもとに独立した状態を持っている。

Scenes

その他

  • Document-Based AppをSwiftUIで実現するために、DocumentGroup Sceneが導入された
  • macOSの設定画面をサポートするSettings Scene Typeが導入された。
    • アプリメニューに設定項目を追加し、設定画面をのウインドウをサポートする
  • コンテキストメニューを提供するCommandsも追加された
saharasahara

SwiftUI in Swift Playgrounds

https://developer.apple.com/documentation/swift-playgrounds/live-preview
https://yutailang0119.hatenablog.com/entry/swiftui-with-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
saharasahara

特定の位置(ビュー)までスクロールしたい

ScrollViewReaderを使う。

https://developer.apple.com/documentation/swiftui/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()
}
saharasahara

プレビュー

ローカリゼーション

NSLocalizableStringがXcode 14.1の Previewだとうまく機能しない😢

https://developer.apple.com/documentation/xcode/previewing-localizations

端末の向き

iOS15以降は、previewInterfaceOrientationで指定できる。

iOS14のシミュレータベースだと、うまく機能しない😢

参考

https://qiita.com/shtnkgm/items/6f32eba5c9955cc055a7

Xcode15以降のPreview

https://sarunw.com/posts/xcode-previews-with-uikit-appkit-in-xcode-15/

saharasahara

Grid

https://developer.apple.com/wwdc20/10031

LazyGrid

https://developer.apple.com/documentation/swiftui/lazyvgrid

https://developer.apple.com/documentation/swiftui/lazyhgrid

GridItem

https://developer.apple.com/documentation/swiftui/griditem

ウインドウの幅に合わせて、表示する行 or 列の数を変えたい時は、GridItem(.adaptive(minimum:))を使う

最低限の幅をminimum引数で指定し、行数 or 列数は ウインドウ幅によって決定される。

fixed, flexible, adaptive

https://paigeshin1991.medium.com/swiftui-grid-fixed-vs-flexible-vs-adaptive-253e9b12da34