12
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Swift、SwiftUI】実装ノウハウのリンク集(随時更新)

Last updated at Posted at 2020-09-02

#1. 内容
 アプリ開発のために調べた情報の中で、今後も参照する機会が多いと思うものをストックするものです。技術的な知見を惜しげもなく公開してくださる皆様のおかげでアプリを作れています。ありがとうございます:pray:

#2. リンク集

公式

(1) API Design Guidelines
(2) SwiftUI Tutorials
(3) Apple公式
  (a) Human Interface Guidelines
  (b) App Store Reviewガイドライン
  (c) Marketing Resources and Identity Guidelines

リファレンス

SwiftUI

(1) var body の中で条件分岐を使用するときの実装方法
(2) プロパティ・ラッパーの初期化方法
  (stackoverflow)@State付きの変数の値の初期化が無効になる
  Swift Binding変数の初期化の仕方について
(3) Property Wrappersの使い方の詳しい説明
  Property Wrappersとデータへのアクセス方法
  SwiftUIの機能 @State, @ObservedObject, @EnvironmentObjectの違い
(4) SwiftUIでUIImagePickerControllerを表示
(5) カスタマイズしたmodifier: if #available iOS 14
(6) Listの_.onMove_ での sourcedestination の値について
(7) SwiftUI View Lifecycle
(8) Data Validation in SwiftUI 2.0
(9) 大きいサイズのオブジェクトを配置するとZStackのalignmentが効かない
(12) 状態監視からメソッドを実行する方法
(13) TextFieldの検証
(14) NavigationLink to perform an action
(15) iOS13で @StateObject の機能を実装する
(16) View を UIImage に変換
(17) 子ビューから親ビューのメソッドを呼ぶ
(18) NavigationViewで2つ以上前の画面に戻る
(19) UIViewRepresentable のカスタム modifier
(20) Binding<Int> を Binding<String> に変換

キーボード

(1) キーボードの扱い
(2) TextField Keyboard Focus, Camera Feed, Extending Comparable など Tips
(3) ignoresSafeArea の第一引数と SwiftUI のキーボード避け
(4) iOS 14 SwiftUI Keyboard lifts view automatically

通信

(1) (Qiita)【Swift】URLSessionまとめ
(2) (Qiita)Swiftの有名画像キャッシュライブラリを比較してみた
(3) SwiftUIでURLから画像取得

QRCodeの処理

(1) QRコード生成
  SwiftUIでQRコードの表示
  QR Codes Are Simple in Swift
(2) QRコードを読み取り
  Camera preview and a QR-code Scanner in SwiftUI
  SwiftUIでAVFundationを導入する【Video Capture偏】
  【Swift】QRコードを読み取って文字列を取得する *UIKit
(3) AVCapturePhotoOutputの実装例 *UIkit
  Swiftでカメラアプリを作成する(1)
  Swiftでカメラアプリを作成する(2)
  [iPhone] AVCaptureVideoDataOutput ビデオで静止画撮影する
  [iPhone] Camera撮影, AVCapturePhotoOutput
(4) 『QRコード』の仕組みの説明。記録データの大きさや誤り訂正機能など
  『QRコード』の仕組みを解説! 決済やスマホでの読み取り方・作成方法も

Xcode

(1) Xcodeのリファクタリング機能の詳しい紹介
(2) Xcodeの使い方
(3) Crash Report
(4) ドキュメントコメントの残し方まとめ
(5) Xcodeシミュレーター管理コマンド
(6) Preview の表示設定
(7) PlaygroundでSwiftUIのViewを描画する

基本構文

(1) Swift演算子まとめ
(2) ”append() vs. appending()” や ”isEmpty() vs. count == 0” など、類似した構文の使い分け
(3) scaledToFill()とかscaledToFit()とかaspectRatio(_:contentMode:)等の使い分け
(4) zip, dump, sequence などの関数の使い方
(5) Swift: Understand Recursive Enum in Five Minutes
(6) lazyの使い方
(7) forEach, forIn, forEnumerated の使い方
(8) Swift Attributes @discardableResult など
(9) 文字列の先頭・末尾の1文字を取得する方法
(10) Strong, weak, and unowned references — Swift 5 (iOS)
(11) 10 Different Ways to Loop in Swift
(12) Swift Protocols With Associated Types and Generics

#3. Tips

ScrollViewとLongPressGestureを併用

ScrollView(.horizontal, showsIndicators: false){
   HStack{
      ForEach(self.imageArray, id: \.self) {image in
         Image(uiImage: image)
            .resizable()
            .frame(maxWidth: 200, maxHeight: 200, alignment: .center)
            .aspectRatio(contentMode: .fit)
            .onTapGesture {} // ScrollViewとLongPressGestureを併用するために設置
            .onLongPressGesture(minimumDuration: 0.5) {
               // do something
            }
      }
   }
}

Longpress and list scrolling


NavigationBarの装飾

init(){
        // 背景を透明にする
        UINavigationBarAppearance().configureWithTransparentBackground() // 背景を透明にする
        //UINavigationBar.appearance().barTintColor = .white // navigationBarの背景色
        UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.blue] // タイトル文字装飾
        // navigationBarの下線と影を消す
        UINavigationBar.appearance().setBackgroundImage(UIImage(), for: UIBarMetrics.default)
        UINavigationBar.appearance().shadowImage = UIImage()
    }

【SwiftUI】NavigationView タイトルカスタマイズ方法
Navigation Barのボーダーを削除する


TabViewの挙動

TabViewNavigationBar を組み合わせた際におかしな挙動があるので、注意。なお、 TabBarItem の装飾には、 accentColor モディファイアを使う

TabBarView
import SwiftUI
struct TabBarView: View {    
    // 選択していない「アイコンとテキスト」の色定義
    init() {
        UITabBar.appearance().unselectedItemTintColor = UIColor.gray
    }

    @State var selectedTab = 0

    var body: some View {
        ZStack {
            TabView(selection: $selectedTab) {
                ContentView()
                    .tabItem {
                        Image(systemName: "star.circle.fill")
                        Text("Dummy")
                    }
                    .tag(0)
                ContentView()
                    .tabItem {
                        Image(systemName: "star.circle.fill")
                        Text("Card")
                    }
                    .tag(1)
            }.accentColor(.black)
        }
    }
}
ContentView
import SwiftUI
struct ContentView: View {
    var body: some View {
        NavigationView {
            ZStack {
                Text("Hello")
            }.navigationBarTitle(Text("Nav_Title"))
            // ↑ここが正しい
        }//.navigationBarTitle(Text("Nav_Title"))
         // ↑ここにタイトルを設定(あるいはNavItemだけを設定してタイトルはnil)すると、何故か選択時のtabBarItemのテキストに設定される
    }
}

参考リンク


_UIViewRepresentable_に準拠した構造体のプロパティ変化を連係する

 下記のようにシングルトンパターンを採用する場合、UIViewRepresentable_に準拠した構造体(下記の例の_MyView)を再利用できないので、_DataSource_クラスでプロパティを必要なだけ用意する必要があります。

class DataSource: ObservableObject {
    static let dataSource = DataSource() // シングルトンとし、「MyView」のプロパティ変化を「ContentView」に伝える
    @Published var foo: String = ""
}

struct MyView: UIViewRepresentable {
    
    // TextViewをSwiftUI化する例
    final class Coordinator: NSObject, UITextViewDelegate {

    // updateUIViewで値を渡してもいいかもしれない
    func textViewDidChange(_ textView: UITextView) {
       let dataSource = DataSource.shared
       dataSource.foo = textView.text
    }

}

struct ContentView: View {
    @ObservedObject var dataSource = DataSource.shared
    var body: some View {
       // 通知されたプロパティ変化をViewで表示
    }
}
    

Prevent the Reload of Whole SwiftUI Body on State Changes by Decoupling Independent Views

出典:SwiftUI Tips and Tricks
Being a state-driven framework, whenever a state is changed, all the views in the SwiftUI body get refreshed — including the ones that weren’t bound to that state.

Sometimes, you might wish there was a way to avoid refreshing the whole body. Luckily, you can do this by separating the views that aren’t dependent on the state, as shown below:

image.png
In the code above, the RandomText no longer refreshes the text when the bottom sheet is presented or dismissed.


NotificationCenterの購読 @SwiftUI

発行側(class)
extension Notification.Name {
    static let bar = Notification.Name("bar")
}

class hoge{
   func fuga(){
      let userInfo = ["foo": true] as [String: Bool]
      NotificationCenter.default.post(name: .bar, object: nil, userInfo: userInfo)
   }
}
購読側(struct)
import SwiftUI
struct ContentView: View {
   var body: some View {
      VStack{
         // show something
      }
      .onReceive( NotificationCenter.default.publisher(for: .bar))
      { obj in
         if let userInfo = obj.userInfo, let info = userInfo["foo"] as? Bool{
            // do something
         }
      }
   }
}

AndroidのToast風UIの実装

import SwiftUI

struct Toast<Presenting>: View where Presenting: View {
    /// The binding that decides the appropriate drawing in the body.
    @Binding var isShowing: Bool
    /// The view that will be "presenting" this toast
    let presenting: () -> Presenting
    /// The text to show
    let text: Text
    let delay: TimeInterval = 2
    let toastHeight: CGFloat = 60

    var body: some View {
        
        if self.isShowing {
            DispatchQueue.main.asyncAfter(deadline: .now() + self.delay) {
                withAnimation {
                    self.isShowing = false
                }
            }
        }

        return GeometryReader { geometry in

            ZStack(alignment: .bottom) {

                self.presenting()
                    .blur(radius: self.isShowing ? 1 : 0)

                VStack {
                    self.text
                }
                .frame(width: geometry.size.width / 2,
                       height: self.toastHeight)
                .background(Color.secondary.colorInvert())
                .foregroundColor(Color.primary)
                .cornerRadius(20)
                .transition(.identity)
                .opacity(self.isShowing ? 1 : 0)

            }.padding(.bottom, 30)
        }
    }
}

extension View {

    func toast(isShowing: Binding<Bool>, text: Text) -> some View {
        Toast(isShowing: isShowing,
              presenting: { self },
              text: text)
    }

}

struct ContentView: View {
    @State var showToast: Bool = false
    var body: some View {
       VStack(){
          // show something
       }.toast(isShowing: self.$showToast, text: Text("Pls Put Something!"))
       
       // ボタンアクションなどで、"showToast"をonにする
    }
}

SwiftUI: Global Overlay That Can Be Triggered From Any View


Drawing a Border with Rounded Corners for Buttons and Text

overlayモディファイアを使う。
Drawing a Border with Rounded Corners for Buttons and Text


Leverage Frame Modifier for Filling Views

出典:SwiftUI Tips and Tricks
By default, views occupy minimal space on the screen. For instance, the following TextView wraps itself:
image.png

To expand the views to fill the super view space, we can leverage the frame modifier and set the maxWidth and maxHeight properties in it to infinity, as shown below:
image.png


enumを使って複数のモーダル遷移を実装する

 Identifiableに準拠したenumを作成し、その値を.sheet(item:)に設定する。itemなので、nilかどうかで、モーダル遷移する(このため、ラッパープロパティはオプショナル型)。

enum Sheet: Identifiable {
    case info
    case settings
    
    var id: Int {
      hashValue
    }
  }
  struct ContentView: View {
    @State var activeSheet: Sheet?
    
    var body: some View {
      VStack(spacing: 50) {
        Text("Main View")
          .font(.largeTitle)
        
        Button(action: {
          activeSheet = .info
        }, label: {
          Label("Show Info View", systemImage: "info.circle")
        })
        
        Button(action: {
          activeSheet = .settings
        }, label: {
          Label("Show Settings View", systemImage: "gear")
        })
      }
      .sheet(item: $activeSheet) { sheet in
        switch sheet {
        case .info:
          InfoView()
        case .settings:
          SettingsView()
        }
      }
    }
  }

extensionを作成する場合は、こんな感じ。

extension Sheet {
    var modalView: AnyView {
      switch self {
      case .info:
        return AnyView(InfoView())
      case .settings:
        return AnyView(SettingsView())
      }
    }
}

// 使い方1
.sheet(item: $activeSheet) { $0.modalView }
// 使い方2
.sheet(item: $activeSheet, content: \.modalView)

Modifierのスタイルをテンプレート化

image.png

Packageの編集

Video: Creating Swift Packages
実装例: Editing A Swift Package

(一部抜粋)
image.png

12
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?