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

MyMemoWiki

SwiftUI

提供: MyMemoWiki
2021年4月24日 (土) 01:40時点におけるPiroto (トーク | 投稿記録)による版 (→‎余白の取り方)
ナビゲーションに移動 検索に移動

| 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とナビゲーションとプレビュー

Tips

余白の取り方


Swiftui padding1.png

  Text(host.host)
       .padding()
       .background(Color.green)

Swiftui padding2.png

  Text(host.host)
       .background(Color.green)
       .padding()

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

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のサイズ情報を取得しレコードごとにグリッドの列のサイズを揃える


Swiftui grid layout1.png Swiftui grid layout2.png

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


画面部品の追加方法


SwiftUIライブラリ


SwiftUIX

SwiftUIアプリケーション開発の不足を補うSwiftUIX