😽

「UIKit丸出し」で十分な人向けのSwiftUI

2020/11/23に公開
  • 「UIKit丸出し」で十分な人向けのSwiftUI(iOS)のコンポーネントカタログです
  • 基本コード
    • ContentView が本体
    • ContentView_Previews はプレビューの表示
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

  • プレビューでコンポーネント部分だけ表示する場合はContentView()に.previewLayout(.sizeThatFits)を指定する
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().previewLayout(.sizeThatFits)
    }
}

  • Option + Command + Enter で 右側のCanvas(プレビュー)の Show/Hide が切り替えられる
  • プレビューは良く壊れてエラーになる、 Option + Command + P で再描画できる

共通

Viewを複数配置する

  • VStack/HStack/ZStack を使って入れ子にする必要がある

VStack

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.red)
                .multilineTextAlignment(.center)
                .padding(.all)
            Text("Hello, SwiftUI!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.blue)
                .multilineTextAlignment(.center)
                .padding(.all)
        }
    }
}

HStack

struct ContentView: View {
    var body: some View {
        HStack {
            Text("Hello, world!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.red)
                .multilineTextAlignment(.center)
                .padding(.all)
            Text("Hello, SwiftUI!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.blue)
                .multilineTextAlignment(.center)
                .padding(.all)
        }
    }
}

ZStack

struct ContentView: View {
    var body: some View {
        ZStack {
            Text("Hello, world!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.red)
                .multilineTextAlignment(.center)
                .padding(.all)
            Text("Hello, SwiftUI!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.blue)
                .multilineTextAlignment(.center)
                .padding(.all)
        }
    }
}

Text

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .font(.title)
                .fontWeight(.medium)
                .foregroundColor(Color.red)
                .multilineTextAlignment(.center)
                .padding(.all)
        }
    }
}

TextField

  • 入力可能なテキスト
struct ContentView: View {
    @State var text: String = "hello"
    var body: some View {
        TextField(text, text: $text)
    }
}

SecureField

struct ContentView: View {
    @State private var username: String = ""
    @State private var password: String = ""

    var body: some View {
        VStack {
            TextField(
                "User name (email address)",
                text: $username)
                .autocapitalization(.none)
                .disableAutocorrection(true)
                .border(Color(UIColor.separator))
                .padding()
            SecureField(
                "Password",
                text: $password
            ) {
                print("username: \(username), password: \(password)")
            }
            .border(Color(UIColor.separator))
            .padding()
        }
    }
}

TextEditor

  • 複数行の入力が可能なTextField
  • iOS 14.0+
struct ContentView: View {
    @State private var fullText: String = "This is some editable text..."

    var body: some View {
        TextEditor(text: $fullText)
            .padding()
    }
}

Label

  • iOS 14.0+
struct ContentView: View {
    var body: some View {
        Label(
            title: { Text("Camera") },
            icon: { Image(systemName: "camera") }
        )
    }
}

Button

struct ContentView: View {
    var body: some View {
        VStack {
            Button("Button1") {
                print("Button1")
            }
            Button(action: {
                print("Button2")
            }, label: {
                Text("Button2")
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(Color.green)
            })
        }
    }
}

EditButton

@State private var fruits = [
    "Apple",
    "Banana",
    "Papaya",
    "Mango"
]

var body: some View {
    NavigationView{
        List {
            ForEach(
                fruits,
                id: \.self
            ) { fruit in
                Text(fruit)
            }
            .onDelete { self.deleteFruit(at :$0) }
            .onMove { self.moveFruit(from: $0, to: $1) }
        }
        .navigationTitle("Fruits")
        .toolbar { EditButton() }
    }
}

Image

struct ContentView: View {
    let image: UIImage = UIImage(systemName: "camera")!
    var body: some View {
        Image(uiImage: image)
    }
}

  • UIImageがnilを許容する場合はifを使って以下のような書き方ができる
struct ContentView: View {
    @State var image: UIImage?
    var body: some View {
        if let image = self.image {
            Image(uiImage: image)
        }
        else {
            Button("show") {
                image = UIImage(systemName: "camera")
            }
        }
    }
}

List

struct ContentView: View {
    let values = ["1","2","3","4","5"]
    var body: some View {
        List {
            ForEach(0..<values.count) { index in
                Text(values[index])
            }
        }
    }
}

Form

struct ContentView: View {
    var body: some View {
        Form {
            Text("Text1")
            Text("Text2")
            Text("Text3")
        }
    }
}

Menu

  • ポップアップメニュー
  • iOS 14.0+
struct ContentView: View {
    var body: some View {
        Menu("Actions") {
            Button("Duplicate", action: dummy)
            Button("Rename", action: dummy)
            Button("Delete…", action: dummy)
            Menu("Copy") {
                Button("Copy", action: dummy)
                Button("Copy Formatted", action: dummy)
                Button("Copy Library Path", action: dummy)
            }
        }
    }

    func dummy() {
    }
}

Link

  • タップするとブラウザが起動する
  • iOS 14.0+
struct ContentView: View {
    var body: some View {
        Link("SwiftUI",
              destination: URL(string: "https://developer.apple.com/documentation/swiftui")!)
    }
}

Toggle

struct ContentView: View {
    @State private var vibrateOnRing = false

    var body: some View {
        Toggle(isOn: $vibrateOnRing) {
            Text("Vibrate on Ring")
        }
        .padding()
    }
}

Slider

struct ContentView: View {
    @State private var speed = 50.0
    @State private var isEditing = false

    var body: some View {
        VStack {
            Slider(
                value: $speed,
                in: 0...100,
                onEditingChanged: { editing in
                    isEditing = editing
                }
            )
            Text("\(speed)")
                .foregroundColor(isEditing ? .red : .blue)
        }
    }
}

Stepper

struct ContentView: View {
    var body: some View {
        StepperView()
    }
}

struct StepperView: View {
    @State private var value = 0
    let step = 5
    let range = 1...50

    var body: some View {
        VStack {
            Text("Current: \(value) in \(range.description)")
            Stepper(value: $value,
                in: range,
                step: step) {
                Text("stepping by \(step)")
            }
        }
        .padding(10)
    }
}

ColorPicker

  • iOS 14.0+
struct ContentView: View {
    @State private var bgColor =
        Color(.sRGB, red: 0.98, green: 0.9, blue: 0.2)

    var body: some View {
        VStack {
            ColorPicker("Alignment Guides", selection: $bgColor)
                .padding()
        }
    }
}

ProgressView

  • iOS 14.0+
struct ContentView: View {
    @State private var progress = 0.5
    var body: some View {
        VStack {
            ProgressView(value: progress)
            Button("More", action: { progress += 0.05 })
            ProgressView {
                Text("Now Processing...")
            }
        }
    }
}

TabView

struct ContentView: View {
    var body: some View {
        TabView {
            Text("The First Tab")
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
                }
            Text("Another Tab")
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
                }
            Text("The Last Tab")
                .tabItem {
                    Image(systemName: "3.square.fill")
                    Text("Third")
                }
        }
        .font(.headline)
    }
}

NavigationView

struct ContentView: View {
    let values = ["1","2","3","4","5"]
    var body: some View {
        NavigationView {
            List {
                ForEach(0..<values.count) { index in
                    NavigationLink(destination: Text(values[index])) {
                        Text(values[index])
                    }
                }
            }
            .navigationTitle("Camera")
            .navigationBarItems(trailing: Button(action: {
                    print("camera")
                }, label: {
                    Image(systemName: "camera")
                })
            )
        }
    }
}


Picker

  • ドラム(wheel)型、デフォルト
struct ContentView: View {
    @State private var selection = 0
    let items = ["1", "2", "3"]

    var body: some View {
        Picker(selection: $selection, label: Text("Picker")) {
            ForEach(0 ..< items.count) { num in
                Text(self.items[num])
            }
        }
    }
}

  • メニュー型、タップするとメニューが表示される
struct ContentView: View {
    @State private var selection = 0
    let items = ["1", "2", "3"]

    var body: some View {
        Picker(selection: $selection, label: Text("Picker")) {
            ForEach(0 ..< items.count) { num in
                Text(self.items[num])
            }
        }
        .pickerStyle(MenuPickerStyle())
    }
}

  • セグメント型
struct ContentView: View {
    @State private var selection = 0
    let items = ["1", "2", "3"]

    var body: some View {
        Picker(selection: $selection, label: Text("Picker")) {
            ForEach(0 ..< items.count) { num in
                Text(self.items[num])
            }
        }
        .pickerStyle(SegmentedPickerStyle())
    }
}

DatePicker

  • 日付、時刻の選択
struct ContentView: View {
    @State private var date = Date()

    var body: some View {
        DatePicker(selection: $date, label: { Text("DatePicker") })
    }
}

  • ホイール(WheelDatePickerStyle)
struct ContentView: View {
    @State private var date = Date()

    var body: some View {
        DatePicker(selection: $date, label: { Text("DatePicker") })
            .datePickerStyle(WheelDatePickerStyle())
    }
}

  • グラフィカル(GraphicalDatePickerStyle)
struct ContentView: View {
    @State private var date = Date()

    var body: some View {
        DatePicker(selection: $date, label: { Text("DatePicker") })
            .datePickerStyle(GraphicalDatePickerStyle())
    }
}

Alert

struct ContentView: View {
    @State var showAlert = false
    var body: some View {
        Button("Alert") {
            showAlert = true
        }
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("This is alert"))
        })
    }
}

dismissButton

struct ContentView: View {
    @State var showAlert = false
    var body: some View {
        Button("Alert") {
            showAlert = true
        }
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("title"), message: Text("message"), dismissButton: .default(Text("了解")){
                print("default")
            })
        })
    }
}

dismissButton(cancel)

struct ContentView: View {
    @State var showAlert = false
    var body: some View {
        Button("Alert") {
            showAlert = true
        }
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("title"), message: Text("message"), dismissButton: .cancel(Text("キャンセル")){
                print("cancel")
            })
        })
    }
}

primaryButton と secondaryButton

struct ContentView: View {
    @State var showAlert = false
    var body: some View {
        Button("Alert") {
            showAlert = true
        }
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("title"), message: Text("message"), primaryButton: .default(Text("button1"), action: {
                print("button1")
            }), secondaryButton: .default(Text("button2"), action: {
                print("button2")
            }))
        })
    }
}

ActionSheet

struct ContentView: View {
    @State var showSheet = false
    var body: some View {
        Button("Sheet") {
            showSheet = true
        }
        .actionSheet(isPresented: $showSheet, content: {
            ActionSheet(title: Text("title"), message: Text("message"), buttons: [
                .default(Text("button1"), action: {
                    print("button1")
                }),
                .default(Text("button2"), action: {
                    print("button1")
                }),
                .default(Text("button3"), action: {
                    print("button1")
                }),
                .cancel()
            ])
        })
    }
}

Stroke

struct ContentView: View {
    var body: some View {
        Path { path in
            path.addRect(CGRect(x: 100, y: 100, width: 200, height: 200))
        }
        .stroke(Color.red, lineWidth: 2)
    }
}

RoundedRectangle

  • Image()の左上を起点にアノテーションを描画、オフセットとサイズを指定する
  • ZStack(alignment: .topLeading)
struct ContentView: View {
    var body: some View {
        ZStack(alignment: .topLeading) {
            Image(systemName: "person.fill")
                .resizable()
                .scaledToFit()
            RoundedRectangle(cornerRadius: 10)
                .fill(Color.blue)
                .frame(width:200, height: 200)
                .offset(x:100, y:100)
                .opacity(0.5)
        }
    }
}

Spacer

  • スペースを入れる
struct ContentView: View {
    var body: some View {
        VStack {
            Spacer()
            Text("Hello, world!")
                .padding()
            Spacer()
            Text("Hello, swift!")
                .padding()
            Spacer()
        }
    }
}

Font

struct ContentView: View {
    var body: some View {
        VStack {
            VStack {
                Text(".largeTitle")
                    .font(.largeTitle)
                Text(".title")
                    .font(.title)
                Text(".title2")
                    .font(.title2)
                Text(".title3")
                    .font(.title3)
                Text(".headline")
                    .font(.headline)
                Text(".subheadline")
                    .font(.subheadline)
                Text(".body")
                    .font(.body)
                Text(".callout")
                    .font(.callout)
                Text(".caption")
                    .font(.caption)
                Text(".caption2")
                    .font(.caption2)
            }
            VStack {
                Text(".footnote")
                    .font(.footnote)
            }
        }
    }
}

Discussion