「SwiftUI」の版間の差分
ナビゲーションに移動
検索に移動
(→ボタンサイズ) |
(→Tips) |
||
28行目: | 28行目: | ||
====[https://www.typea.info/blog/index.php/2021/01/23/swiftui_tutorial_list_navigation/ Listとナビゲーションとプレビュー]==== | ====[https://www.typea.info/blog/index.php/2021/01/23/swiftui_tutorial_list_navigation/ Listとナビゲーションとプレビュー]==== | ||
− | == | + | ==レイアウト== |
===[https://t32k.me/mol/log/margin-padding-swiftui/ 余白の取り方]=== | ===[https://t32k.me/mol/log/margin-padding-swiftui/ 余白の取り方]=== | ||
---- | ---- |
2021年4月24日 (土) 23:34時点における版
| 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とナビゲーションとプレビュー
レイアウト
余白の取り方
- 余白の取り方
- 記述箇所によって、表示が変わる
- backgroundにpaddingを指定する(cssのmargin的な効果)
Text(host.host) .background(Color.green) .padding()
- Textにpaddingを指定することになる(cssのpadding的効果)
Text(host.host) .padding() .background(Color.green)
ボタンサイズ
- ボタンのサイズを内容にフィットさせたい
Button(action: {}) { VStack{ : } .padding() .border(Color.blue, width: 3) }
- .buttonStyle(PlainButtonStyle()) を指定
Button(action: {}) { VStack{ : } .padding() .border(Color.blue, width: 3) }.buttonStyle(PlainButtonStyle())
親Viewのサイズ情報を取得する
- https://qiita.com/masa7351/items/0567969f93cc88d714ac
- https://www.hackingwithswift.com/quick-start/swiftui/how-to-make-two-views-the-same-width-or-height
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) } }
コードサンプル(コンポーネント)
Button
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)
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
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
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
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
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) } }
画像
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() } }
図形
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
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)
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にオブジェクトを表示
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 = "" }
コードサンプル(ロジック)
Observable(@ObservedObject,@Published,@State)
- データクラスはObservableObjectプロトコル準拠とする。
- 監視対象とするプロパティに@Published属性を付加する。
- データクラスのインスタンスは@ObservedObject属性を付加してViewの中で宣言する
- 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
© 2006 矢木浩人