| ページ一覧 | ブログ | twitter |  書式 | 書式(表) |

MyMemoWiki

SwiftUI

提供: MyMemoWiki
ナビゲーションに移動 検索に移動

| Swift | Mac | Xcode | Swift Sample |

SwiftUI

  • SwiftUI
  • SwiftUI Documents
  • macos tutorials
  • 1セットのツールとAPIを使用するだけで、あらゆるAppleデバイス向けのユーザーインターフェイスを構築
  • 宣言型シンタックスを使
  • 宣言型のスタイルは、アニメーションなどの複雑な概念にも適用

デザインツール

  • Xcodeには、SwiftUIでのインターフェイス構築をドラッグ&ドロップのように簡単に行える直感的な新しいデザインツールが含まれています
  • デザインキャンバスでの編集内容と、隣接するエディタ内のコードはすべて完全に同期されます

ドラッグ&ドロップ

  • ユーザーインターフェイス内のコンポーネントの位置は、キャンバス上でコントロールをドラッグするだけで調整できます

ダイナミックリプレースメント

  • wiftのコンパイラとランタイムはXcode全体に完全に埋め込まれているため、Appは常にビルドされ実行されます
  • 表示されるデザインキャンバスは、単にユーザーインターフェイスに似せたものではなく、実際のAppそのもの
  • Xcodeは編集したコードを実際のAppに直接組み入れることができます

プレビュー

  • プレビューを1つまたは複数作成して、サンプルデータを取得できる

Swift UI チュートリアルをやってみる

プロジェクト作成〜TextViewのカスタマイズ

Custom Image Viewの作成

Xcodeを使ってmacOS プログラミングとplaygroundの作成

Listとナビゲーションとプレビュー

コードサンプル(コンポーネント)

Button


Swift sample button.png

import Foundation
import SwiftUI

struct ButtonView: View {

    @State var cnt:Int = 0;
    var body: some View {
        VStack {
            Button(action: {
                self.cnt += 1;
                print("print \(self.cnt)")
            })
            {
                Text("Button+1 (\(self.cnt))")
            }
            .padding(.horizontal, 25.0)
            .font(.largeTitle)
            .foregroundColor(Color.white)
            .background(Color.green)
            .cornerRadius(15, antialiased: true)
            Divider()
            Button("Button+2 (\(self.cnt))") {
                self.cnt += 2;
            }
            .font(.largeTitle)
            .foregroundColor(.white)
            .background(
                Capsule()
                    .foregroundColor(Color.blue)
                    .frame(width: 200, height: 60, alignment: .center)
            )
        }
    }
}

Toggle(@State)


Swift sample toggle.png

import SwiftUI

struct ToggleView: View {
    @State var isOn = true
    var body: some View {
        VStack {
            Toggle(isOn: $isOn) {
                Text("On/Off")
                    .font(.title)
            }
            .fixedSize()
            .padding()
            
            /* https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/
             */
            Button(action: {
                withAnimation {
                    self.isOn.toggle()
                }
            }) {
                Image(systemName: self.isOn ? "applewatch" : "applewatch.slash")
                    .font(.system(size: 60))
                    .frame(width: 100, height: 100)
                    .imageScale(.large)
                    .rotationEffect(.degrees(isOn ? 0 : 360))
                    
            }
        }
    }
}

Stepper


Swift sample stepper.png

import SwiftUI

struct StepperView: View {
    @State var cnt = 0;
    var body: some View {
        VStack {
            Stepper(value: $cnt, in: 0 ... 5) {
                Text("Stepper-\(self.cnt)")
            }.frame(width: 200)
            Stepper(
                onIncrement: {
                    self.cnt += 5;
                },
                onDecrement: {
                    self.cnt -= 3;
                },
                label: {
                    Text("Stepper-\(self.cnt)")
                }
            ).frame(width: 200)
        }
    }
}

Alert


Swift sample alert.png

import SwiftUI

struct AlertView: View {
    @State var isAlert = false;
    var body: some View {
        Button(action: {
            self.isAlert = true
        }) {
            Text("Alert")
                .foregroundColor(.white)
        }.background(
            Capsule()
                .foregroundColor(.blue)
                .frame(width: 100, height: 40)
        ).alert(isPresented: $isAlert, content: {
            Alert(title: Text("Title"),message: Text("Messge"),
                primaryButton: .default(Text("OK"), action: {}),
                secondaryButton: .cancel(Text("Cancel"),action: {}))
        })
    }
}

Tab


Swift sample tab.png


struct TabbedView: View {
    @State var selection = 0
    var body: some View {
        TabView(selection: $selection) {
            ButtonView().tabItem {
                Text("Item1")
            }.tag(1)
            ToggleView().tabItem {
                Text("Item2")
            }.tag(2)
        }
    }
}

Binding


Swift sample binding.png

import SwiftUI

struct BindingView: View {
    @State var isChecked1: Bool = false
    @State var isChecked2: Bool = false
    var body: some View {
        VStack {
            CheckImageButton(isChecked: $isChecked1)
            CheckImageButton(isChecked: $isChecked2)
        }
    }
}

struct CheckImageButton: View {
    @Binding var isChecked: Bool
    var body: some View {
        Button(action: {
            self.isChecked.toggle()
        }) {
            Image(systemName: isChecked ?
                "person.crop.circle.badge.checkmark":
                "person.crop.circle")
                .foregroundColor(
                    isChecked ? .blue : .gray)
        }
        .imageScale(.large)
        .frame(width: 40)
    
    }
}

画像


Swiftui image sample.png

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image("add_component_swiftui").resizable()
                .aspectRatio(contentMode:.fill)
                .frame(width: 400, height: 400)
                .scaleEffect(1.2)
                .offset(x: -60, y: 0)
                .clipped()
                .overlay(
                    Text("SwiftUI Sample")
                        .font(.title)
                        .foregroundColor(.red)
                )
                .clipShape(Circle()/)
                .shadow(radius: 10)
        }
    }
}

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

図形


Swiftui sample figure.png

import SwiftUI

struct Figure: View {
    var body: some View {
          VStack {
              Circle()
                  .foregroundColor(.blue)
                  .frame(width: 100.0, height:100.0)
              Ellipse()
                  .foregroundColor(.green)
                  .frame(width: 200, height: 100.0)
              Rectangle()
                  .foregroundColor(.orange)
                  .frame(width: 100.0, height: 100.0)
                  .rotationEffect(.degrees(45))
            }
    }
}

struct Figure_Previews: PreviewProvider {
    static var previews: some View {
        Figure()
    }
}

List


Swiftui list sample.png

import SwiftUI

struct ListView: View {
    var body: some View {
        NavigationView {
            List {
                Text("List Item")
                Text("List Item")
                HStack {
                    Image("add_component_swiftui")
                        .frame(width: 100, height: 100)
                        .scaleEffect(0.2)
                        .aspectRatio(contentMode:.fit)
                        .clipped()
                        .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
                       
                }
                Text("List Item")
                Text("List Item")
            }.navigationBarTitle("List Title")
        }
    }
}

struct ListView_Previews: PreviewProvider {
    static var previews: some View {
        ListView()
    }
}

List(繰り返し、Section)


Swiftui list section.png

import SwiftUI

let items = ["item1","item2","item3","item4","item5"];
struct EmbededList: View {
    var body: some View {
        VStack {
            List(0..<items.count) { idx in
                Text(items[idx])
            }
            .frame(height: 300.0)
            List {
                Section(header: Text("Section1")) {
                    ForEach(0 ..< items.count) { idx in
                        Text(items[idx])
                    }
                }
                Section(header: Text("Section2")) {
                    ForEach(0 ..< items.count) { idx in
                        Text(items[idx])
                    }
                }
            }
        }
    }
}

struct EmbededList_Previews: PreviewProvider {
    static var previews: some View {
        EmbededList()
    }
}

Listにオブジェクトを表示


Swift ui object load to list.png

import SwiftUI

struct ContentView: View {
    @ObservedObject var hosts = HostList()
    var body: some View {
        VStack {
            HStack {
                Button(action: {
                    WoLService().arp(hosts:self.hosts)
                }) {
                    Text("arp -a")
                }.padding()
            }
            Divider()
            List (hosts.hosts, id: \.ip){ host in
                Text(host.host)
                Text(host.ip)
                Text(host.macaddr)
            }
        }
    }
}
import Foundation

class HostList : ObservableObject {
    @Published var hosts: [Host] = []
}

class Host {
    var host: String = ""
    var ip: String = ""
    var macaddr: String = ""
}

親Viewのサイズ情報を取得しレコードごとにグリッドの列のサイズを揃える


struct HostView : View {
    var host: WoL.Host
    var body: some View {
        GeometryReader { geo in
            HStack() {
                Text(host.host)
                    .padding()
                    .frame(width:  geo.size.width * 0.33 , alignment: .leading)
                    .frame(maxHeight: .infinity)
                    .background(Color.red)
                Text(host.ip)
                    .padding()
                    .frame(width: geo.size.width * 0.33 , alignment: .leading)
                    .frame(maxHeight: .infinity)
                    .background(Color.green)
                Text(host.macaddr)
                    .padding()
                    .frame(width: geo.size.width * 0.33 , alignment: .leading)
                    .frame(maxHeight: .infinity)
                    .background(Color.yellow)
            }.fixedSize(horizontal: false, vertical: true)
            
        }.frame(height: 60)
    }
}

コードサンプル(ロジック)

Observable(@ObservedObject,@Published,@State)


  1. データクラスはObservableObjectプロトコル準拠とする。
  2. 監視対象とするプロパティに@Published属性を付加する。
  3. データクラスのインスタンスは@ObservedObject属性を付加してViewの中で宣言する

Swift sample observable.png

  • Publish
import Foundation

class PublishObject: ObservableObject {
    @Published var counter: Int = 0
    
    var timer = Timer()
    
    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            self.counter += 1
        }
    }
    
    func stop() {
        timer.invalidate()
    }
    
    func reset() {
        timer.invalidate()
        counter = 0
    }
}
  • Subscribe
import SwiftUI

struct SubscriberView: View {
    @ObservedObject var publisher = PublishObject()
    
    let currentTimer = Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default).autoconnect()
    @State var now = Date()
    
    var body: some View {
        VStack {
            Text("\(self.now.description)")
            HStack {
                Button(action: {
                    self.publisher.start()
                }){
                    Image(systemName: "play")
                }.padding()
                Button(action: {
                    self.publisher.stop()
                }){
                    Image(systemName: "pause")
                }.padding()
                Button(action: {
                    self.publisher.reset()
                }){
                    Image(systemName: "backward.end")
                }.padding()
            }
            .frame(width:200)
        
            Text("\(self.publisher.counter)")
            
        }.font(.largeTitle)
        .onReceive(currentTimer) { date in
            self.now = date
        }
    }
}

バックグラウンドからUIを操作する


  • observableobj が、ObservableObject の派生クラス
  • contentフィールドに、@Published アノテーション
  • Viewで、@ObservedObjectを付与しインスタンスを生成
  • 上記で、バックグラウンドから、observableobj.contentを操作すると、UIはメインスレッドから触るように怒られる。

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

  • DispatchQueue.main.syncで囲む
DispatchQueue.main.sync {
    observableobj.content = text
}

Tips


画面部品の追加方法