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

MyMemoWiki

「SwiftUI」の版間の差分

提供: MyMemoWiki
ナビゲーションに移動 検索に移動
867行目: 867行目:
 
=====[https://github.com/SwiftUIX/SwiftUIX SwiftUIX]=====
 
=====[https://github.com/SwiftUIX/SwiftUIX SwiftUIX]=====
 
[https://qiita.com/yosshi4486/items/3d92f81feaabc1049b4c SwiftUIアプリケーション開発の不足を補うSwiftUIX]
 
[https://qiita.com/yosshi4486/items/3d92f81feaabc1049b4c SwiftUIアプリケーション開発の不足を補うSwiftUIX]
 +
 +
===ファイル選択===
 +
----
 +
<pre>
 +
let dialog = NSOpenPanel();
 +
 +
dialog.title                  = "Choose a file| Our Code World";
 +
dialog.showsResizeIndicator    = true;
 +
dialog.showsHiddenFiles        = false;
 +
dialog.allowsMultipleSelection = false;
 +
dialog.canChooseDirectories = false;
 +
 +
if (dialog.runModal() ==  NSApplication.ModalResponse.OK) {
 +
let result = dialog.url // Pathname of the file
 +
 +
if (result != nil) {
 +
let path: String = result!.path
 +
print(path);
 +
}
 +
 +
} else {
 +
// User clicked on "Cancel"
 +
return
 +
}
 +
</pre>

2021年11月20日 (土) 00:35時点における版

| 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的な効果)

Swiftui padding2.png

  1. Text(host.host)
  2. .background(Color.green)
  3. .padding()
  • Textにpaddingを指定することになる(cssのpadding的効果)

Swiftui padding1.png

  1. Text(host.host)
  2. .padding()
  3. .background(Color.green)

ボタンサイズ


  • ボタンのサイズを内容にフィットさせたい

Swiftui button size1.png

  1. Button(action: {}) {
  2. VStack{
  3. :
  4. }
  5. .padding()
  6. .border(Color.blue, width: 3)
  7. }
  • .buttonStyle(PlainButtonStyle()) を指定

Swiftui button size2.png

  1. Button(action: {}) {
  2. VStack{
  3. :
  4. }
  5. .padding()
  6. .border(Color.blue, width: 3)
  7. }.buttonStyle(PlainButtonStyle())

親Viewのサイズ情報を取得する


Swiftui grid layout1.png Swiftui grid layout2.png

  1. struct HostView : View {
  2. var host: WoL.Host
  3. var body: some View {
  4. GeometryReader { geo in
  5. HStack() {
  6. Text(host.host)
  7. .padding()
  8. .frame(width: geo.size.width * 0.33 , alignment: .leading)
  9. .frame(maxHeight: .infinity)
  10. .background(Color.red)
  11. Text(host.ip)
  12. .padding()
  13. .frame(width: geo.size.width * 0.33 , alignment: .leading)
  14. .frame(maxHeight: .infinity)
  15. .background(Color.green)
  16. Text(host.macaddr)
  17. .padding()
  18. .frame(width: geo.size.width * 0.33 , alignment: .leading)
  19. .frame(maxHeight: .infinity)
  20. .background(Color.yellow)
  21. }.fixedSize(horizontal: false, vertical: true)
  22. }.frame(height: 60)
  23. }
  24. }

影をつける

---

  • shadowを設定すると、内部のコンポーネント全てに影がつく、compositingGroup を指定することで、背景だけに影をつけることができる
  1. :
  2. .background()
  3. .compositingGroup()
  4. .shadow(radius: 10)

Card View


Swiftui card layout1.png Swiftui card layout2.png

  1. let columns =
  2. [GridItem(.adaptive(minimum: 250, maximum: 800))]
  3. ScrollView {
  4. LazyVGrid(columns: columns, spacing:10) {
  5. ForEach(hosts.hosts, id: \.macaddr) { host in
  6. HostView(host:host)
  7. }
  8. } .padding(20)

Toolbar


300lx

  1. ScrollView {
  2. LazyVGrid(columns: columns) {
  3. ForEach(hosts.hosts, id: \.ip) { host in
  4. HostView(host:host)
  5. }
  6. } .padding(20)
  7. }.toolbar {
  8. ToolbarItem(placement: .automatic) {
  9. Button("arp -a") {
  10. WoLService().arp(hosts:self.hosts)
  11. }
  12. }
  13. ToolbarItem(placement: .automatic) {
  14. Button("load") {
  15. WoLService().load(hosts:self.hosts)
  16. }
  17. }
  18. }

データ

UserDefaults


ドキュメントパス


  1. let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
  2. print("Document path: \(paths)")
  • 出力
  1. Document path: [file:///Users/hirotoyagi/Library/Containers/info.typea.WoL/Data/Documents/]

ファイル格納先


View間データ受け渡し


ObservableObject を経由して親子ViewでAlertの表示フラグを共有

Swiftui share object.png

  • ObservableObjectを共有して、変更をSubscribeする
  1. struct HostListView: View {
  2. @ObservedObject var hosts = HostList()
  3. @ObservedObject var param = Param()
  4. var body: some View {
  5. VStack {
  6. let columns =
  7. [GridItem(.adaptive(minimum: 250, maximum: 800))]
  8. ScrollView {
  9. LazyVGrid(columns: columns, spacing:10) {
  10. ForEach(hosts.hosts, id: \.id) { host in
  11. HostView(host:host).environmentObject(param)
  12. }
  13. } .padding(20)
  14. }.alert(isPresented: $param.isSaveAlert, content: {
  15. Alert(title: Text("Title"),message: Text("Messge"),
  16. primaryButton: .default(Text("OK"), action: {}),
  17. secondaryButton: .cancel(Text("Cancel"),action: {}))
  18. })
  19. }
  20. }
  21. }
  22. struct HostView : View {
  23. @ObservedObject var host: WoL.Host
  24. @EnvironmentObject var param: Param
  25. var body: some View {
  26. VStack{
  27. HStack {
  28. Button(action: {
  29. self.param.isSaveAlert = true
  30. }) {
  31. Image(systemName: "square.and.arrow.down")
  32. }.help(Text("save"))
  33. }
  34. :
  35. }
  36. }
  37. }
  38.  
  39. class Param : ObservableObject {
  40. @Published var isSaveAlert: Bool = false
  41. }

@Binding を経由して親子ViewでAlertの表示フラグを共有

Swiftui share object.png

  • @Binding でView間で変数を共有
  • 呼び出し側は@State
  1. struct HostListView: View {
  2. @ObservedObject var hosts = HostList()
  3. @State var isSaveAlert = false
  4.  
  5. var body: some View {
  6. VStack {
  7. let columns =
  8. [GridItem(.adaptive(minimum: 250, maximum: 800))]
  9. ScrollView {
  10. LazyVGrid(columns: columns, spacing:10) {
  11. ForEach(hosts.hosts, id: \.id) { host in
  12. HostView(host:host, isSaveAlert: $isSaveAlert, alertMessage: $alertMessage)
  13. }
  14. } .padding(20)
  15. }.alert(isPresented: $isSaveAlert, content: {
  16. Alert(title: Text("Title"),message: Text(alertMessage),
  17. primaryButton: .default(Text("OK"), action: {}),
  18. secondaryButton: .cancel(Text("Cancel"),action: {}))
  19. })
  20. }
  21. }
  22. }
  23. struct HostView : View {
  24. @ObservedObject var host: WoL.Host
  25. @Binding var isSaveAlert: Bool
  26. var body: some View {
  27. VStack{
  28. HStack {
  29. Button(action: {
  30. self.isSaveAlert = true
  31. }) {
  32. Image(systemName: "square.and.arrow.down")
  33. }.help(Text("save"))
  34. :
  35. }
  36. }
  37. }
  38. }
@Binding使用時のPreview
  • @Stateかつ、staticで宣言
  • $で変数を渡す
  1. struct HostView_Previews: PreviewProvider {
  2. @State static var isAlert = true
  3.  
  4. static var previews: some View {
  5. let host = WoL.Host(host: "test.local", ip: "192.168.0.1", macaddr: "aa:bb:cc:dd:ee:ff", comment: "")
  6. HostView(host: host, isAlert: $isAlert)
  7. }
  8. }

メニュー

メインメニューに別のViewを開くメニューを追加


Swift ui main menu.png

  • .commandsを記述
  1. @main
  2. struct WoLApp: App {
  3. var body: some Scene {
  4. WindowGroup {
  5. ContentView()
  6. }.commands {
  7. CommandGroup(after: CommandGroupPlacement.appInfo) {
  8. Divider()
  9. NavigationLink(destination: PreferenceView()) {
  10. Text("preferences")
  11. }
  12. }
  13. }
  14. }
  15. }

図形

Capsule


Swiftui capsule.png

  1. VStack{
  2. :
  3. }
  4. .padding()
  5. .background(
  6. Capsule(style: .continuous)
  7. .foregroundColor(Color.white)
  8. )
  9. .shadow(radius:10 )

RoundedRectangle


Swiftui roundedRectangle.png

  1. VStack{
  2. :
  3. }
  4. .padding()
  5. .background(
  6. RoundedRectangle(cornerRadius: 20)
  7. .foregroundColor(Color.white)
  8. )
  9. .shadow(radius:10 )

画像

SF Symbol アイコン


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

Button


Swift sample button.png

  1. import Foundation
  2. import SwiftUI
  3.  
  4. struct ButtonView: View {
  5.  
  6. @State var cnt:Int = 0;
  7. var body: some View {
  8. VStack {
  9. Button(action: {
  10. self.cnt += 1;
  11. print("print \(self.cnt)")
  12. })
  13. {
  14. Text("Button+1 (\(self.cnt))")
  15. }
  16. .padding(.horizontal, 25.0)
  17. .font(.largeTitle)
  18. .foregroundColor(Color.white)
  19. .background(Color.green)
  20. .cornerRadius(15, antialiased: true)
  21. Divider()
  22. Button("Button+2 (\(self.cnt))") {
  23. self.cnt += 2;
  24. }
  25. .font(.largeTitle)
  26. .foregroundColor(.white)
  27. .background(
  28. Capsule()
  29. .foregroundColor(Color.blue)
  30. .frame(width: 200, height: 60, alignment: .center)
  31. )
  32. }
  33. }
  34. }

Toggle(@State)


Swift sample toggle.png

  1. import SwiftUI
  2.  
  3. struct ToggleView: View {
  4. @State var isOn = true
  5. var body: some View {
  6. VStack {
  7. Toggle(isOn: $isOn) {
  8. Text("On/Off")
  9. .font(.title)
  10. }
  11. .fixedSize()
  12. .padding()
  13. /* https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/
  14. */
  15. Button(action: {
  16. withAnimation {
  17. self.isOn.toggle()
  18. }
  19. }) {
  20. Image(systemName: self.isOn ? "applewatch" : "applewatch.slash")
  21. .font(.system(size: 60))
  22. .frame(width: 100, height: 100)
  23. .imageScale(.large)
  24. .rotationEffect(.degrees(isOn ? 0 : 360))
  25. }
  26. }
  27. }
  28. }

Stepper


Swift sample stepper.png

  1. import SwiftUI
  2.  
  3. struct StepperView: View {
  4. @State var cnt = 0;
  5. var body: some View {
  6. VStack {
  7. Stepper(value: $cnt, in: 0 ... 5) {
  8. Text("Stepper-\(self.cnt)")
  9. }.frame(width: 200)
  10. Stepper(
  11. onIncrement: {
  12. self.cnt += 5;
  13. },
  14. onDecrement: {
  15. self.cnt -= 3;
  16. },
  17. label: {
  18. Text("Stepper-\(self.cnt)")
  19. }
  20. ).frame(width: 200)
  21. }
  22. }
  23. }

Alert


Swift sample alert.png

  1. import SwiftUI
  2.  
  3. struct AlertView: View {
  4. @State var isAlert = false;
  5. var body: some View {
  6. Button(action: {
  7. self.isAlert = true
  8. }) {
  9. Text("Alert")
  10. .foregroundColor(.white)
  11. }.background(
  12. Capsule()
  13. .foregroundColor(.blue)
  14. .frame(width: 100, height: 40)
  15. ).alert(isPresented: $isAlert, content: {
  16. Alert(title: Text("Title"),message: Text("Messge"),
  17. primaryButton: .default(Text("OK"), action: {}),
  18. secondaryButton: .cancel(Text("Cancel"),action: {}))
  19. })
  20. }
  21. }

Tab


Swift sample tab.png

  1.  
  2. struct TabbedView: View {
  3. @State var selection = 0
  4. var body: some View {
  5. TabView(selection: $selection) {
  6. ButtonView().tabItem {
  7. Text("Item1")
  8. }.tag(1)
  9. ToggleView().tabItem {
  10. Text("Item2")
  11. }.tag(2)
  12. }
  13. }
  14. }

Tab切り替えイベント


  1. @State var selectedTabIndex = 1;
  2. var body: some View {
  3. TabView(selection: $selectedTabIndex) {
  4. arpListView.tabItem {
  5. Text("Tab Label 1")
  6. }.tag(1)
  7. hostListView.tabItem {
  8. Text("Tab Label 2")
  9. }.tag(2)
  10. }
  11. .onChange(of: selectedTabIndex) { value in
  12. print("TAB INDEX \(value)")
  13. }
  14. }

Binding


Swift sample binding.png

  1. import SwiftUI
  2.  
  3. struct BindingView: View {
  4. @State var isChecked1: Bool = false
  5. @State var isChecked2: Bool = false
  6. var body: some View {
  7. VStack {
  8. CheckImageButton(isChecked: $isChecked1)
  9. CheckImageButton(isChecked: $isChecked2)
  10. }
  11. }
  12. }
  13.  
  14. struct CheckImageButton: View {
  15. @Binding var isChecked: Bool
  16. var body: some View {
  17. Button(action: {
  18. self.isChecked.toggle()
  19. }) {
  20. Image(systemName: isChecked ?
  21. "person.crop.circle.badge.checkmark":
  22. "person.crop.circle")
  23. .foregroundColor(
  24. isChecked ? .blue : .gray)
  25. }
  26. .imageScale(.large)
  27. .frame(width: 40)
  28. }
  29. }

画像


Swiftui image sample.png

  1. import SwiftUI
  2.  
  3. struct ContentView: View {
  4. var body: some View {
  5. VStack {
  6. Image("add_component_swiftui").resizable()
  7. .aspectRatio(contentMode:.fill)
  8. .frame(width: 400, height: 400)
  9. .scaleEffect(1.2)
  10. .offset(x: -60, y: 0)
  11. .clipped()
  12. .overlay(
  13. Text("SwiftUI Sample")
  14. .font(.title)
  15. .foregroundColor(.red)
  16. )
  17. .clipShape(Circle()/)
  18. .shadow(radius: 10)
  19. }
  20. }
  21. }
  22.  
  23. struct ContentView_Previews: PreviewProvider {
  24. static var previews: some View {
  25. ContentView()
  26. }
  27. }

図形


Swiftui sample figure.png

  1. import SwiftUI
  2.  
  3. struct Figure: View {
  4. var body: some View {
  5. VStack {
  6. Circle()
  7. .foregroundColor(.blue)
  8. .frame(width: 100.0, height:100.0)
  9. Ellipse()
  10. .foregroundColor(.green)
  11. .frame(width: 200, height: 100.0)
  12. Rectangle()
  13. .foregroundColor(.orange)
  14. .frame(width: 100.0, height: 100.0)
  15. .rotationEffect(.degrees(45))
  16. }
  17. }
  18. }
  19.  
  20. struct Figure_Previews: PreviewProvider {
  21. static var previews: some View {
  22. Figure()
  23. }
  24. }

List


Swiftui list sample.png

  1. import SwiftUI
  2.  
  3. struct ListView: View {
  4. var body: some View {
  5. NavigationView {
  6. List {
  7. Text("List Item")
  8. Text("List Item")
  9. HStack {
  10. Image("add_component_swiftui")
  11. .frame(width: 100, height: 100)
  12. .scaleEffect(0.2)
  13. .aspectRatio(contentMode:.fit)
  14. .clipped()
  15. .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
  16. }
  17. Text("List Item")
  18. Text("List Item")
  19. }.navigationBarTitle("List Title")
  20. }
  21. }
  22. }
  23.  
  24. struct ListView_Previews: PreviewProvider {
  25. static var previews: some View {
  26. ListView()
  27. }
  28. }

List(繰り返し、Section)


Swiftui list section.png

  1. import SwiftUI
  2.  
  3. let items = ["item1","item2","item3","item4","item5"];
  4. struct EmbededList: View {
  5. var body: some View {
  6. VStack {
  7. List(0..<items.count) { idx in
  8. Text(items[idx])
  9. }
  10. .frame(height: 300.0)
  11. List {
  12. Section(header: Text("Section1")) {
  13. ForEach(0 ..< items.count) { idx in
  14. Text(items[idx])
  15. }
  16. }
  17. Section(header: Text("Section2")) {
  18. ForEach(0 ..< items.count) { idx in
  19. Text(items[idx])
  20. }
  21. }
  22. }
  23. }
  24. }
  25. }
  26.  
  27. struct EmbededList_Previews: PreviewProvider {
  28. static var previews: some View {
  29. EmbededList()
  30. }
  31. }

Listにオブジェクトを表示


Swift ui object load to list.png

  1. import SwiftUI
  2.  
  3. struct ContentView: View {
  4. @ObservedObject var hosts = HostList()
  5. var body: some View {
  6. VStack {
  7. HStack {
  8. Button(action: {
  9. WoLService().arp(hosts:self.hosts)
  10. }) {
  11. Text("arp -a")
  12. }.padding()
  13. }
  14. Divider()
  15. List (hosts.hosts, id: \.ip){ host in
  16. Text(host.host)
  17. Text(host.ip)
  18. Text(host.macaddr)
  19. }
  20. }
  21. }
  22. }
  1. import Foundation
  2.  
  3. class HostList : ObservableObject {
  4. @Published var hosts: [Host] = []
  5. }
  6.  
  7. class Host {
  8. var host: String = ""
  9. var ip: String = ""
  10. var macaddr: String = ""
  11. }

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

Observable(@ObservedObject,@Published,@State)


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

Swift sample observable.png

  • Publish
  1. import Foundation
  2.  
  3. class PublishObject: ObservableObject {
  4. @Published var counter: Int = 0
  5. var timer = Timer()
  6. func start() {
  7. timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
  8. self.counter += 1
  9. }
  10. }
  11. func stop() {
  12. timer.invalidate()
  13. }
  14. func reset() {
  15. timer.invalidate()
  16. counter = 0
  17. }
  18. }
  • Subscribe
  1. import SwiftUI
  2.  
  3. struct SubscriberView: View {
  4. @ObservedObject var publisher = PublishObject()
  5. let currentTimer = Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default).autoconnect()
  6. @State var now = Date()
  7. var body: some View {
  8. VStack {
  9. Text("\(self.now.description)")
  10. HStack {
  11. Button(action: {
  12. self.publisher.start()
  13. }){
  14. Image(systemName: "play")
  15. }.padding()
  16. Button(action: {
  17. self.publisher.stop()
  18. }){
  19. Image(systemName: "pause")
  20. }.padding()
  21. Button(action: {
  22. self.publisher.reset()
  23. }){
  24. Image(systemName: "backward.end")
  25. }.padding()
  26. }
  27. .frame(width:200)
  28. Text("\(self.publisher.counter)")
  29. }.font(.largeTitle)
  30. .onReceive(currentTimer) { date in
  31. self.now = date
  32. }
  33. }
  34. }

動的に検索

Swiftui dynamic search.png

  1. struct HostListView: View {
  2. @ObservedObject var hosts = HostList()
  3. @ObservedObject var param = HostViewParameter()
  4. // 検索キーワード
  5. @State var searchKeyword = ""
  6. var body: some View {
  7. VStack {
  8. let columns =
  9. [GridItem(.adaptive(minimum: 250, maximum: 800))]
  10. ScrollView {
  11. LazyVGrid(columns: columns, spacing:10) {
  12. // Arrayにfilterを適用
  13. ForEach(hosts.hosts.filter({ (host) -> Bool in
  14. if searchKeyword != "" {
  15. return host.host.contains(searchKeyword) ||
  16. host.ip.contains(searchKeyword) ||
  17. host.comment.contains(searchKeyword)
  18. }
  19. return true
  20. }), id: \.id) { host in
  21. HostView(host:host).environmentObject(param)
  22. }
  23. } .padding(20)
  24. }.toolbar {
  25. :
  26. ToolbarItem(placement: .automatic) {
  27. TextField("search...", text: $searchKeyword)
  28. }
  29. }
  30. }
  31. }
  32. }

バックグラウンドから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で囲む
  1. DispatchQueue.main.sync {
  2. observableobj.content = text
  3. }

Tips


画面部品の追加方法


SwiftUIライブラリ


SwiftUIX

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

ファイル選択


  1. let dialog = NSOpenPanel();
  2.  
  3. dialog.title = "Choose a file| Our Code World";
  4. dialog.showsResizeIndicator = true;
  5. dialog.showsHiddenFiles = false;
  6. dialog.allowsMultipleSelection = false;
  7. dialog.canChooseDirectories = false;
  8.  
  9. if (dialog.runModal() == NSApplication.ModalResponse.OK) {
  10. let result = dialog.url // Pathname of the file
  11.  
  12. if (result != nil) {
  13. let path: String = result!.path
  14. print(path);
  15. }
  16. } else {
  17. // User clicked on "Cancel"
  18. return
  19. }