66
64

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UIKitでの書き方をSwiftUIに直した物をまとめてみた。

Last updated at Posted at 2021-08-02

はじめに

こんにちは@kaneko77です。
次からの案件がSwiftUIを採用をしているみたいで、これまでUIKitオンリーで書いていたので色々SwiftUIの書き方をまとめました。
SwiftUIという新しい土俵(?)に上がって少し不安もありますが、UIKItでのナレッジがあるのでどうにかなるはず....
私と同じようにこれまでUIKItオンリーでやってきたエンジニアさん多いと思います。
採用率増えてますし、Storyboard消してコードだけでレイアウト組んでるプロダクトが大半ですもんね。
ということで共有初めていきます!!

こんな方対象

  • これまでUIKITでレイアウト組んでたけどSwiftUIやることになった方
  • SwiftUIについてチートシート的な感じで知っておきたい方

UIKitとSwiftUIの違い一覧

まずは一覧

引用

紹介

ちょっと先に余談

私の端末Xcode13 betaなんですが、このXcodeからSwiftUIで新しくプロジェクト作成するとinfo.plistとかSceneDelegate.swiftとかないので注意
まだXcode13の参考書とか出てるわけでもなく情報も少ないのでbetaでない方のXcodeでやることおすすめします。

一応なんで無くなったのって人のために説明 or 記事記事共有しておきます。

  • info.plistが無くなった理由 参照
  • SceneDelegateAppDelegateは前のバージョンではlifecycleって項目が新規フォロジェクトを立ち上げるとあったのですがその項目自体がなくなったから自動的にSwiftUI Appが適用されることになりました。参照

余談以上です。

Text編

それではHello Worldから

定義の仕方

UIKIT.swift
let testText: UILabel = {
   let text = UILabel()
    text.text = "おはよう世界!"
    return text
}()
SwiftUI.swift
var body: some View {
    Text("おはよう世界!")
}

文字色の変更

UIKIT.swift
let text = UILabel()
text.text = "おはよう世界!"
text.textColor = .red
SwiftUI.swift
Text("おはよう世界!")
    .foregroundColor(.red)

背景色の変更

UIKIT.swift
let text = UILabel()
text.text = "おはよう世界!"
text.backgroundColor = .black
SwiftUI.swift
Text("おはよう世界!")
    .background(Color.black)

余白

UiKitでは例えばUILabelの余白を表示しようとなるとサブクラス化したりと面倒でした..
SwiftUIからはこりゃまたびっくり!!
詳しくはこちら参照

SwiftUI.swift
Text("はひふへほ")
    .padding(55)
    .background(Color.red)

文字の太さの変更

UIKIT.swift
let text = UILabel()
text.text = "おはよう世界!"
text.font = .boldSystemFont(ofSize: 20)
SwiftUI.swift
Text("おはよう世界!")
    .bold()
    .font(.system(size: 20))

複数行に対応する

UIKIT.swift
let text = UILabel()
text.text = "おはよう\n世界!"
text.numberOfLines = 0
SwiftUI.swift
Text("おはよう\n世界!")
    .lineLimit(nil)

画像編

UIKIT.swift
let imageView: UIImageView = {
   let image = UIImageView()
    image.image = UIImage(systemName: "person.fill")
    return image
}()
self.view.addSubview(imageView)
SwiftUI.swift
Image(systemName: "person.fill")

Label編

UIKitでは画像とテキストを一緒に表示する時、attributeなどで割と面倒くさく表現していました。
SwiftUIでは専用の物が用意されています。これまためちゃくちゃ簡単にできます。
画像はシンボルを使います。

SwiftUI.swift
Label("テストで表示", systemImage: "person.fill")
// Viewを入れることもできます
Label(
    title: {
        Text("色変更").foregroundColor(.red)
    },
    icon: {
        Image(systemName: "person.fill").foregroundColor(.blue)
    }
)

コンテンツの表示編

SwiftUIでのコントロール(並べる)は最大10個まで

横に並べる

UIKIT.swift
let stackView: UIStackView = {
    let stack = UIStackView()
    stack.axis = .horizontal
    return stack
}()
let testText: UILabel = {
   let text = UILabel()
    text.text = "おはよう世界!"
    return text
}()
let testText1: UILabel = {
   let text = UILabel()
    text.text = "こんにちは世界!"
    return text
}()
addSubview(stackView)
stackView.addArrangedSubview(testText)
stackView.addArrangedSubview(testText1)
SwiftUI.swift
var body: some View {
    HStack{
        Text("おはよう世界!")
        Text("こんにちは世界!")
    }
}

縦に並べる

UIKIT.swift
let stackView: UIStackView = {
    let stack = UIStackView()
    stack.axis = .vertical
    return stack
}()
let testText: UILabel = {
   let text = UILabel()
    text.text = "おはよう世界!"
    return text
}()
let testText1: UILabel = {
   let text = UILabel()
    text.text = "こんにちは世界!"
    return text
}()
addSubview(stackView)
stackView.addArrangedSubview(testText)
stackView.addArrangedSubview(testText1)
SwiftUI.swift
var body: some View {
    VStack{
        Text("おはよう世界!")
        Text("こんにちは世界!")
    }
}

親コンポーネントのレイアウト変更

表現が難しいですね。親コンポーネント=まとめているViewということです。

UIKIT.swift
let stackView: UIStackView = {
    let stack = UIStackView()
    stack.axis = .vertical
    stack.backgroundColor = .red
    return stack
}()
let testText: UILabel = {
   let text = UILabel()
    text.text = "おはよう世界!"
    return text
}()
let testText1: UILabel = {
   let text = UILabel()
    text.text = "こんにちは世界!"
    return text
}()
addSubview(stackView)
stackView.addArrangedSubview(testText)
stackView.addArrangedSubview(testText1)
SwiftUI.swift
var body: some View {
    VStack{
        Text("おはよう世界!")
        Text("こんにちは世界!")
    }.background(Color.red)
}

繰り返し条件を使ってレイアウトを表示

UIKIT.swift
let stackView: UIStackView = {
    let stack = UIStackView()
    stack.axis = .vertical
    return stack
}()
let testTexts: [UILabel] = {
    let text = UILabel()
    text.text = "hoge"
    let text1 = UILabel()
    text1.text = "huga"
    let text2 = UILabel()
    text2.text = "hugahoge"
    return [text, text1, text2]
}()
addSubview(stackView)
testTexts.forEach{
    stackView.addArrangedSubview($0)
}
SwiftUI.swift
let testTexts: [Text] = [
        Text("hoge"),
        Text("huga"),
        Text("hugahoge")
    ]
    var body: some View {
        VStack{
            ForEach(0 ..< testTexts.count){ self.testTexts[$0] }
        }.background(Color.red)
    }

区切り線

UIKitだとこれUIViewで高さを1とかにして表現して作ってましたがSwiftUIだと標準で用意されているみたいです。

UIKIT.swift
let stackView: UIStackView = {
    let stack = UIStackView()
    stack.axis = .vertical
    return stack
}()
let testText: UILabel = {
   let text = UILabel()
    text.text = "hoge"
    return text
}()
let testText1: UILabel = {
   let text = UILabel()
    text.text = "huga"
    return text
}()
let spacer: UIView = {
    let view = UIView()
    view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 1)
    return view
}()
addSubview(stackView)
[testText, spacer, testText1].forEach{
    stackView.addArrangedSubview($0)
}
SwiftUI.swift
var body: some View {
    VStack{
        Text("hoge")
        Divider()
        Text("huga")
    }
}

コンテンツをView全体に表示する

UIKitだとまずview作ってそれにAutoLayout適用させて私は全体表示表現してました。
しかしこれまた簡単にできるものが用意されてます。
詳しくはこちら参照

SwiftUI.swift
struct ContentView: View {
    private var colorView: some View {
        Color.init(.red)
    }
    var body: some View {
        colorView
            .edgesIgnoringSafeArea(.all)
    }
}

// 上記だったこんな風でいいです。(あくまでUIKit的に上記で書きました)
struct ContentView: View {
    var body: some View {
        Color.red
            .edgesIgnoringSafeArea(.all)
    }
}

Button編

呼び出し方

UIKIT.swift
let testButton: UIButton = {
    let button = UIButton()
    button.setTitle("ボタン", for: .normal)
    button.addTarget(
        self,
        action: #selector(tapped(_:)),
        for: .touchUpInside
    )
    return button
}()
@objc func tapped(_ sender : Any) {
    print("タップされた")
}

SwiftUI.swift
var body: some View {
    VStack{
        Button(action: {
            print("タップされた")
        }, label: {
            Text("ボタン")
        })
        Spacer(minLength: 0)
    }
}

TextField編

$のマークがPHPにしか見えないすね...
私はPHPちょっと案件で触ってたんですけど苦い思い出が蘇りました笑

呼び出し方

UIKIT.swift
let testButton: UIButton = {
    let button = UIButton()
    button.setTitle("ボタン", for: .normal)
    button.backgroundColor = .red
    button.layer.cornerRadius = 24
    return button
}()
let textField: UITextField = {
    let input = UITextField()
    input.placeholder = "プレースホルダー"
    return input
}()
@objc func tapped(_ sender : Any) {
    print("入力欄の中身", textField.text)
}
SwiftUI.swift
@State var input = ""
var body: some View {
    VStack{
        Button(action: {
            print("入力欄の中身", input)
        }, label: {
            Text("ボタン")
        })
        .background(Color.red)
        .cornerRadius(24)
        
        TextField("プレースホルダー", text: $input)
            .textFieldStyle(RoundedBorderTextFieldStyle())
    }
}

入力欄のパスワード形式

UIKIT.swift
let textField: UITextField = {
    let input = UITextField()
    input.placeholder = "パスワードを入力してください"
    input.isSecureTextEntry = true
    return input
}()
SwiftUI.swift
@State var pass = ""
var body: some View {
    SecureField("パスワードを入力してください", text: $pass)
}

Form編

私の中で知る限りだとこれはSwiftUIでの新しい要素なのかな?と思います。
下記のような枠で囲むやつの実装です。

SwiftUI.swift
@State var name = ""
@State var pass = ""
var body: some View {
    Text("タイトル").font(.title)
    Form{
        Text("入力内容\n名前:\(name)\nパスワード\(pass)")
            .lineLimit(nil)
        TextField("名前を入力してください", text: $name)
        SecureField("パスワードを入力してください", text: $pass)
    }
    
    Spacer(minLength: 0)
}

##Section編
これは入力できるTableViewとかでみたことがあるUIですね。
SwiftUIだとこんなに簡単にできちゃいます。
headerがあってここに書くとUITableViewでいうセクションみたいにできるというわけですね

SwiftUI.swift
struct ContentView: View {
    @State var name = ""
    @State var pass = ""
    @State var address = ""
    @State var tel = ""
    var body: some View {
        VStack{
            Text("Section編").font(.title)
            Form{
                Section(header: Text("ログイン情報")){
                    TextField("名前を入力してください", text: $name)
                    SecureField("パスワードを入力してください", text: $pass)
                }
                Section(header: Text("パーソナル情報")){
                    TextField("住所", text: $address)
                    SecureField("電話番号", text: $tel)
                }
            }
            
            Spacer(minLength: 0)
        }
    }
}

Table編

これは一番簡単にできると感動した物でした。
ListFormは自身の中にスクロール機能が組み込まれています。

呼び出し

UIKIT.swift
//割と抜粋して書いてます。
class TestViewController: UITabBarDelegate, UITableViewDataSource {
    let data: [String] = ["huga", "hoge", "hugahuga"]
    
    let tableView: UITableView = {
       let table = UITableView(frame: .zero, style: .grouped)
       table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
       return table
    }()
    tableView.delegate = self
    tableView.dataSource = self
    self.view.addSubview(tableView)
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
            ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
        cell.textLabel?.text = self.data[indexPath.row]
        return cell
    }
}
SwiftUI.swift
struct ContentView: View {
    let data: [String] = ["huga", "hoge", "hugahuga"]
    var body: some View {
        VStack{
            Text("Table編").font(.title)
            Form{
                Section(header: Text("Test")){
                    List(data, id:\.self){
                        Text($0)
                    }
                }
            }
            
            Spacer(minLength: 0)
        }
    }
}

カスタムセル

カスタムセルってSwiftUIではないんですかね?
UIKitだとカスタムセルって言われてた物も割と楽にできちゃいます。
びっくりするくらいコードがスッキリですね.....(恐ろしいSwiftUI)
Identifiableプロトコルについて

UIKIT.swift
class CustomUITableViewCell: UITableViewCell {
    
    let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.layer.borderWidth = 1
        stack.layer.borderColor = UIColor.red.cgColor
        return stack
    }()
    let name: UILabel = UILabel()
    let spacer: UIView = {
        let view = UIView()
        view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 1)
        view.backgroundColor = .gray
        return view
    }()
    let age: UILabel = UILabel()
    // 色々抜粋 ...
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: .subtitle, reuseIdentifier: reuseIdentifier )
        addSubview(stackView)
        [name, spacer, age].forEach{ stackView.addArrangedSubview($0) }
        // AutoLayout抜粋 ...
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setCell(name: String, age: String) {
        self.name.text = name
        self.age.text = age
    }
}

struct CustomCell {
    let name: String
    let age: String
}

class TestViewController: UITabBarDelegate, UITableViewDataSource {
    let data: [CustomCell] = [
        CustomCell(name: "山田", age: "24"),
        CustomCell(name: "小島", age: "83"),
        CustomCell(name: "岡部", age: "39")
    ]
    tableView.delegate = self
    tableView.dataSource = self
    self.view.addSubview(tableView)

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CustomUITableViewCell
        cell.setCell(
            name: data[indexPath.row].name,
            age: data[indexPath.row].age
        )
        return cell
    }
}
SwiftUI.swift
struct CustomCell: View, Identifiable {
    var id = UUID()
    let name: String
    let age: String
    
    var body: some View{
        VStack{
            Text(name)
                .bold()
                .font(.headline)
            Divider()
            Text(age)
        }.border(Color.red)
    }
}

struct ContentView: View {
    let data: [CustomCell] = [
        CustomCell(name: "山田", age: "24"),
        CustomCell(name: "小島", age: "83"),
        CustomCell(name: "岡部", age: "39")
    ]
    var body: some View {
        VStack{
            Text("Table編").font(.title)
            Form{
                Section(header: Text("Test")){
                    List(data){ $0 }
                }
            }
            
            Spacer(minLength: 0)
        }
    }
}

セルのタップ処理

UIKIT.swift
//割と抜粋して書いてます。
class TestViewController: UITabBarDelegate, UITableViewDataSource {
    let data: [String] = ["huga", "hoge", "hugahuga"]
    
    let tableView: UITableView = {
       let table = UITableView(frame: .zero, style: .grouped)
       table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
       return table
    }()
    tableView.delegate = self
    tableView.dataSource = self
    self.view.addSubview(tableView)
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
            ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
        cell.textLabel?.text = self.data[indexPath.row]
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("\(data[indexPath.row])が選択されました。")
    }
}
SwiftUI.swift
struct ContentView: View {
    let data: [String] = [
        "huga", "hoge", "hugahuga"
    ]
    var body: some View {
        VStack{
            Text("Tableタップ編").font(.title)
            Form{
                Section(header: Text("Test")){
                    List(data, id:\.self){ element in
                        Text(element)
                            .onTapGesture {
                                print(element,"が選択されました。")
                            }
                    }
                }
            }
            
            Spacer(minLength: 0)
        }
    }
}

スクロールできるView

UIKitUIScrollViewAutoLayout組むのが割とコードで書くの面倒ですよね。
SwiftUIで楽にできます。

呼び出し

UIKIT.swift
let scroll = UIScrollView()
let outline = UIView()
let stackView: UIStackView = {
    let stack = UIStackView()
    stack.axis = .vertical
    return stack
}()
let labels: [UILabel] = {
    let data = ["Test1", "Test2", "Test3", "Test4", "Test5", "Test6", "Test7", "Test8", "Test9", "Test10"]
    return data.map {
        let label = UILabel()
        label.text = $0
        return label
    }
}()
let spacer: UIView = {
    let view = UIView()
    view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 1)
    view.backgroundColor = .gray
    return view
}()
addSubview(scroll)
scroll.addSubview(outline)
outline.addSubview(stackView)
labels.forEach{
    stackView.addArrangedSubview($0)
    stackView.addArrangedSubview(spacer)
}

SwiftUI.swift
struct ContentView: View {
    let data = ["Test1", "Test2", "Test3", "Test4", "Test5", "Test6", "Test7", "Test8", "Test9", "Test10"]
    var body: some View {
        VStack{
            Text("スクロール編").font(.title)
            ScrollView{
                ForEach(data, id: \.self){
                    Text($0)
                        .font(.title)
                        .padding(40)
                    Divider()
                }
            }
            
            Spacer(minLength: 0)
        }
    }
}

Navgation

UIKitではUINavgationControllerを使ってViewControllerに当てはめてましたが
今回からViewに直接埋め込んで使うことになりました。

UIKIT.swift
UINavigationController(rootViewController: TestViewController())
SwiftUI.swift
var body: some View {
    NavigationView{
        Text("ほげ")
    }
}

ナビゲーションのタイトル

UIKIT.swift
self.title = "ナビゲーション"
SwiftUI.swift
var body: some View {
    NavigationView{
        Text("ほげ")
            .navigationBarTitle(Text("ナビゲーション"))
    }
}

画面遷移

UIKIT.swift
// めちゃくちゃ抜粋しました。
// ボタンを押下してpresentする想定
present(DetailViewController.init(), animated: true, completion: nil)
SwiftUI.swift
struct ContentView: View {
    var body: some View {
        NavigationView{
            // 画面遷移
            NavigationLink("画面遷移", destination: DetailView(item: "画面遷移した"))
                .navigationBarTitle("選択画面")
        }
    }
}

struct DetailView: View {
    let item: String
    
    var body: some View {
        Text(item)
    }
}

アラート編

デフォルトアラート

UIKIT.swift
// ボタンを押下したら発火する定
let alert = UIAlertController(title: "表示させたいタイトル", message:  "表示させたいサブタイトル", preferredStyle:  .alert)
let confirmAction = UIAlertAction(title: "確定", style: .default, handler:{
    (action: UIAlertAction!) -> Void in
    print("確定")
})
let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel, handler:{
    (action: UIAlertAction!) -> Void in
    print("キャンセル")
})

[cancelAction, confirmAction].forEach{ alert.addAction($0) }
present(alert, animated: true, completion: nil)

SwiftUI.swift
struct ContentView: View {
    @State private var isShow = false
    var body: some View {
        
        NavigationView{
            Button("アラートの表示") {
                self.isShow = true
            }
            .alert(isPresented: $isShow) {
                Alert(title: Text("表示させたいタイトル"),
                      message: Text("表示させたいサブタイトル"),
                      primaryButton: .default(Text("確定"),action: {
                        print("確定")
                      }),
                      secondaryButton: .cancel(Text("キャンセル"),action: {
                        print("キャンセル")
                      })
                )
            }
            .navigationBarTitle("選択画面")
        }
    }
}

アクションシート

UIKIT.swift
let actionSheet = UIAlertController(title: "Menu", message: "", preferredStyle: .actionSheet)
let action1 = UIAlertAction(title: "表示させたいタイトル1", style: .default, handler: {
    (action: UIAlertAction!) in
    print("表示させたいタイトル1の処理")
})
let action2 = UIAlertAction(title: "表示させたいタイトル2", style: .default, handler: {
    (action: UIAlertAction!) in
    print("表示させたいタイトル2の処理")

})

let close = UIAlertAction(title: "閉じる", style: .destructive, handler: {
    (action: UIAlertAction!) in
    //実際の処理
    print("閉じる")
})
[action1,action2,close].forEach{ actionSheet.addAction($0) }

self.present(actionSheet,animated: true, completion: nil)
SwiftUI.swift
struct ContentView: View {
    @State private var isShow = false
    var body: some View {
        
        NavigationView{
            Button("アラートの表示") {
                self.isShow = true
            }
            .actionSheet(isPresented: $isShow) {
                ActionSheet(title: Text("Menu"), message: Text("メッセージ"), buttons:
                                [
                                    .default(Text("表示させたいタイトル1")) { print("選択肢1") },
                                    .default(Text("表示させたいタイトル2")) { print("選択肢2") },
                                    .cancel(Text("閉じる"), action: { print("閉じる") })
                                ]
                )
            }
            .navigationBarTitle("選択画面")
        }
    }
}

modalの表示

SwiftUI.swift
struct ContentView: View {
    @State private var isShow = false
    var body: some View {
        
        NavigationView{
            VStack{
                Button("モーダルViewを表示"){
                    isShow = true
                }.sheet(isPresented: $isShow) {
                    DetailView()
                }
            }.navigationBarTitle("Modal画面遷移元")
        }
    }
}

全体のmodalの表示

専用のものが用意されているみたいです。

UIKIT.swift
let vc = UITestViewController()
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)
SwiftUI.swift
struct ContentView: View {
    @State private var isShow = false
    var body: some View {
        
        NavigationView{
            VStack{
                Button("モーダルViewを表示"){
                    isShow = true
                }.fullScreenCover(isPresented: $isShow) {
                    DetailView()
                }
            }.navigationBarTitle("Modal画面遷移元")
        }
    }
}

終わりに

まだまだSwiftUIUIKitの違いはあると思いますが、ひとまずは一旦これで終了したいと思います。
ある程度よく使う物たちは共有できたのかなと思います。
UIKitのコードはぱぱっと作ったコードで動確していないのでもしかしたらおかしい動作するかもしれません..
UIKitのところについては雰囲気だけお楽しみください.
そこはご了承よろしくお願いいたします。

UIKitだとあんなに長かったのにってのが学習してる最中終始頭の中でその言葉が流れていました。
本当に便利になったと思います。
これからのSwiftUIライフを楽しみたいと思います!!!
ここまで読んでくださりありがとうございます。
皆さんのお役に少しでも立てましたら幸いです。

66
64
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
64

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?