422
Help us understand the problem. What are the problem?
Organization

[Swift] SwiftUIのチートシート

追記
- iOS15の対応表も追加しました。(間違いある場合はご指摘ください)
- ※注意:本記事は Xcode 13.0 beta の環境で動作確認したものです。正式版では動作が変わる可能性もあります。

前置き

とりあえず、SwiftUIチュートリアルを触り、後半の方はサク読みで一応見てみたが、、、

いざ、自分が作りたいものを考えたときに、あれ、、、画面遷移とかどうすんの?書いてなくね?
っていうのがチュートリアルの所感。。。(書いてあったらごめんなさいw)

ってなわけで、実践的に使えそうなものを調べたり、リファレンス読んでごにょってたりして、、
それをまとめた感じです。

UIKitとの対応表

iOS15~

Views and Controls

SwiftUI UIKit Note
AsyncImage (UIImageView)

View Layout and Presentation

SwiftUI UIKit Note
ControlGroup (UIStackview)
TimelineView (Timer + layoutIfNeeded)

iOS14~

Views and Controls

SwiftUI UIKit Note
TextEditor TextField
SignInWithAppleButton ASAuthorizationAppleIDButton
Menu (iPad UIAlertController) (UIAlertController.Style = .actionsheet)
ColorPicker UIColorPickerViewController
ProgressView UIProgressView
Label UILabel
Link (UIButton)

View Layout and Presentation

SwiftUI UIKit Note
LazyHStack UIStackview NSLayoutConstraint.Axis = .horizontal
LazyVStack UIStackview NSLayoutConstraint.Axis = .vertical
LazyHGrid UICollectionView scrollDirection = .horizontal
LazyVGrid UICollectionView scrollDirection = .vertical
GridItem (UICollectionviewflowlayout)
ScrollViewReader - (UITableView-scrollToRow)
OutlineGroup (NSDiffableDataSourceSnapshot)
DisclosureGroup (NSDiffableDataSourceSnapshot)

iOS13~

Views and Controls

SwiftUI UIKit Note
Text UILabel
TextField UITextFiel
SecureField UITextField isSecureTextEntry = true
Font UIFont
Image UIImageView
Button UIButton
NavigationLink - pushViewController
EditButton (UIBarButtonItem) canEditRowAtIndexPath is true
Toggle UISwitch
Picker UIPickerView
DatePicker UIDatePicker
Slider UISlider
Stepper UIStepper
ViewBuilder -
ViewModifier -

View Layout and Presentation

SwiftUI UIKit Note
HStack UIStackview NSLayoutConstraint.Axis = .horizontal
VStack UIStackview NSLayoutConstraint.Axis = .vertical
ZStack - (CALayer-zPosition)
List UITableView
ForEach (UIScrollView) (forEach)
ScrollView UIScrollView
Axis NSLayoutConstraint.Axis
Form (UIStackView)
Group (UIStackView)
Section (UICollectionReuseableView)
Spacer (UIBarButtonItem)
Divider (UIView) (separatorStyle/separatorColor/separatorEffect/separatorInset)
NavigationView UINavigationController
TabView UITabBarController
Alert
(Deprecated)
UIAlertController UIAlertController.Style = .alert
ActionSheet
(Deprecated)
UIAlertController UIAlertController.Style = .actionSheet
EmptyView -
EquatableView -
AnyView -
TupleView -

補足

Noteに関しては、UIKitで特定の条件下の場合に、SwiftUIと同じになるということを表しています。
「( )」に関しては、近しい役割 or 代替可能ということを表しています。

コード例

コード例は、最低限のコードにしている部分が多いので、その部分はご容赦ください、、、笑
比較のため再現可能な既存コードも書いてますが、必ずしもそれが対応しているというわけではないです。参考程度に。

画面遷移

① Navigation

move1.gif

既存

navigationController?.pushViewController(viewController, animated: true)

SwiftUI

// 遷移元
struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: SubContentView()) {
                Text("Show Next")
            }
        }
    }
}

// 遷移先
struct SubContentView: View {
    var body: some View {
        Text("SubContentView")
    }
}

備考

NavigationViewを配置することで、旧来のUINavigationController(NavigationBar)と同じ役割を担える模様。

② Present

move2.gif

既存

present(viewController, animated: true)

SwiftUI

// 遷移元
struct ContentView: View {
    var body: some View {
        PresentationButton(Text("Present"), destination: SubContentView())   
    }
}

// 遷移先
struct SubContentView: View {
    var body: some View {
        Text("SubContentView")
    }
}

備考

挙動が全く一緒というわけではない。
SwiftUIの方では、次の画面が画面全体に重なるわけではないのに注意。
(※他にpresentに対応するものがちゃんとある、という場合はご教授いただけると、、)

Xcode12、SwiftUI2.0から対応するものが追加されたようです。

public func fullScreenCover<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Content) -> some View where Content : View

プレゼンテーション

画面作成を行うにあたり、簡単なUIがいくつか用意されているので、ここで紹介していく。

①Modal (現在こちらはDeprecatedになりました)

present1.gif

struct ContentView: View {
    @State var modal: Modal?

    var body: some View {
        Button(action: {
            self.modal = Modal(Text("Modal"), onDismiss: {})
        }) {
            Text("Show Modal")
        }.presentation(modal)
    }
}

備考

  • 画面遷移のPresentと同じように見えるが、こちらはあまりカスタマイズできないので、リッチなUIは作れない
  • デフォルト引数としてonDismissのクロージャを持つので、閉じる際に何か処理を追加できるメリットあり

②Popover (現在こちらはDeprecatedになりました)

present2.gif

既存

// UIPopoverPresentationControllerDelegate を使用して

let vc = UIViewController()
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 200, height: 300)
vc.popoverPresentationController?.sourceView = view
vc.popoverPresentationController?.delegate = self
present(vc, animated: true)

SwiftUI

struct ContentView: View {
    @State var popover: Popover?

    var body: some View {
        Button(action: {
            self.popover = Popover(content: Text("Popover"), dismissHandler: {})
        }) {
            Text("Show Popover")
        }.presentation(popover)
    }
}

備考

  • 画面が透過されているが、Modalと挙動は同じ
  • Modalと違いEdgeが設定できるなど、少しだけデザインに幅を持たせられる

③ActionSheet (iOS15からDeprecatedになりました)

present3.gif

既存

let alert = UIAlertController(title: "Title", 
                              message: "Message", 
                              preferredStyle: . actionSheet)

let defaultAction = UIAlertAction(title: "Default", style: .default)
let destructiveAction = UIAlertAction(title: "Destructive", style: .destructive)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)

alert.addAction(defaultAction)
alert.addAction(destructiveAction)
alert.addAction(cancelAction)

present(alert, animated: true)

SwiftUI

struct ActionSheetView: View {

    @State var isShown = false

    var body: some View {
        Button(action: {
            self.isShown = true
        }) {
            Text("Show ActionSheet")
        }.actionSheet(isPresented: $isShown, content: {
            ActionSheet(
                title: Text("Title"),
                message: Text("Message"),
                buttons: [.default(Text("Default")),
                          .destructive(Text("Destructive")),
                          .cancel()]
            )
        })
    }
}

備考

  • UIAlertControllerとして一括りにされていたのが独立した
  • 書き方は前と大体同じ印象

④Alert (iOS15からDeprecatedになりました)

present4.gif

既存

let alert = UIAlertController(title: "Title", preferredStyle: .alert)
present(alert, animated: true)

SwiftUI

struct ContentView: View {
    @State var isShown = false

    var body: some View {
        Button(action: {
            self.isShown = true
        }) {
            Text("Show Alert")
        }.alert(isPresented: $isShown, content: 
            Alert(title: Text("Alert"))
        })
    }
}

備考

  • ①~③と違いBool値をBindする必要がある
  • 基本的にはUIAlertControllerと同じ使い方が可能

ライフサイクル

ライフサイクルというと大袈裟だが、Viewそのものからコントロールできるものが登場。

① onAppear

既存

func viewDidAppear(animated: Bool)

SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Appear").onAppear(perform: {
            // do something
        })
    }
}

② onDisappear

既存

func viewDidDisappear(animated: Bool)

SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Disappear").onDisappear(perform: {
            // do something
        })
    }
}

備考

①と②はチェーンで連続してかける

その他

後書き

もっと良いベストプラクティスがあればコメント欲しいとこです。(本気で)
知見がたまったら、改めてこちらに記載する、、かも、、?

まだBeta版なので変わることありそうだなぁ、、

追記

外人がまとめたものがあるので、ここを見るのが早そう。
SwiftUIチートシート

よければこちらも、、
[Swift] URLSession+Combine+CodableでAPIクライアントを作る

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
422
Help us understand the problem. What are the problem?