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

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

#2. リンク集
## 公式
(1) [API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)
(2) [SwiftUI Tutorials](https://developer.apple.com/tutorials/swiftui/)
(3) Apple公式
　　(a) [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/overview/themes/)
　　(b) [App Store Reviewガイドライン](https://developer.apple.com/jp/app-store/review/guidelines/)
　　(c) [Marketing Resources and Identity Guidelines](https://developer.apple.com/app-store/marketing/guidelines/)


## リファレンス
+ [Swift.org Language Guide](https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html) 
+ [Hacking With Swift](https://www.hackingwithswift.com/quick-start)


## SwiftUI
(1) [_var body_ の中で条件分岐を使用するときの実装方法](https://yanamura.hatenablog.com/entry/2019/09/05/150849)
(2) プロパティ・ラッパーの初期化方法
　　[(stackoverflow)@State付きの変数の値の初期化が無効になる](https://ja.stackoverflow.com/questions/63943/state%E4%BB%98%E3%81%8D%E3%81%AE%E5%A4%89%E6%95%B0%E3%81%AE%E5%80%A4%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%8C%E7%84%A1%E5%8A%B9%E3%81%AB%E3%81%AA%E3%82%8B)
　　[Swift Binding変数の初期化の仕方について](https://ja.stackoverflow.com/questions/66472/swift-binding%E5%A4%89%E6%95%B0%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%AE%E4%BB%95%E6%96%B9%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
(3) Property Wrappersの使い方の詳しい説明
　　[Property Wrappersとデータへのアクセス方法](https://qiita.com/shiz/items/6eaf87fa79499623306a#%E4%BD%BF%E3%81%84%E3%81%A9%E3%81%93%E3%82%8D-7)
　　[SwiftUIの機能 @State, @ObservedObject, @EnvironmentObjectの違い](https://www.isoroot.jp/blog/2381/)
(4) [SwiftUIでUIImagePickerControllerを表示](https://medium.com/fantageek/how-to-show-image-picker-in-swiftui-ed0cf5d6797)
(5) [カスタマイズしたmodifier: if #available iOS 14](https://developer.apple.com/forums/thread/652827)
(6) [Listの_.onMove_ での _source_ と _destination_ の値について](https://www.yururiwork.net/%E3%80%90swiftui%E3%80%91onmove%E6%99%82%E3%81%AE-source-%E3%81%A8-destination-%E3%81%AE%E5%80%A4%E3%81%8C%E4%B8%A6%E3%81%B3%E6%9B%BF%E3%81%88%E6%96%B9%E5%90%91%E3%81%AE%E9%81%95%E3%81%84%E3%81%A7/)
(7) [SwiftUI View Lifecycle](https://vadimbulavin.medium.com/swiftui-view-lifecycle-e2b361669b78)
(8) [Data Validation in SwiftUI 2.0](https://medium.com/better-programming/data-validation-in-swiftui-2-0-790cf6dbe49a)
(9) [大きいサイズのオブジェクトを配置するとZStackのalignmentが効かない](https://stackoverflow.com/questions/61978464/swiftui-large-image-in-zstack-breaks-alignment-of-other-layers)
(12) [状態監視からメソッドを実行する方法](https://medium.com/better-programming/three-ways-to-react-to-state-changes-in-swiftui-a30545c72361)
(13) [TextFieldの検証](https://qiita.com/MaShunzhe/items/f3ee3a16e3e6c4512fc4)
(14) [NavigationLink to perform an action](https://stackoverflow.com/questions/57666620/is-it-possible-for-a-navigationlink-to-perform-an-action-in-addition-to-navigati)
(15) [iOS13で ```@StateObject``` の機能を実装する](https://qiita.com/k-yamada-github/items/b90c65ac3bbf71730f7f)
(16) [View を UIImage に変換](https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image)
(17) [子ビューから親ビューのメソッドを呼ぶ](https://www.javaer101.com/ja/article/51017598.html)
(18) [NavigationViewで２つ以上前の画面に戻る](https://stackoverflow.com/questions/57334455/swiftui-how-to-pop-to-root-view/59662275#59662275)
(19) [UIViewRepresentable のカスタム modifier](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-modifiers-for-a-uiviewrepresentable-struct)
(20) [Binding\<Int> を Binding\<String> に変換](https://mjeld.com/swiftui-int-to-binding-string/)
 
## キーボード
(1) [キーボードの扱い](https://riccardocipolleschi.medium.com/ios-tricks-automatic-keyboard-handling-c96e5245de1)
(2) [TextField Keyboard Focus, Camera Feed, Extending Comparable　など Tips](https://medium.com/better-programming/10-helpful-tips-for-writing-cleaner-swift-and-swiftui-code-5a84e5407269)
(3) [ignoresSafeArea の第一引数と SwiftUI のキーボード避け](https://qiita.com/maiyama18/items/840258cbd3bc3f10b93c)
(4) [iOS 14 SwiftUI Keyboard lifts view automatically](https://stackoverflow.com/a/64016326)

## 通信
(1) [(Qiita)【Swift】URLSessionまとめ](https://qiita.com/shiz/items/09523baf7d1cd37f6dee#urlsessiondatatask)
(2) [(Qiita)Swiftの有名画像キャッシュライブラリを比較してみた](https://qiita.com/hcrane/items/422811dfc18ae919f8a4)
(3) [SwiftUIでURLから画像取得](https://www.vadimbulavin.com/asynchronous-swiftui-image-loading-from-url-with-combine-and-swift/)


## QRCodeの処理
(1) QRコード生成
　　[SwiftUIでQRコードの表示](https://qiita.com/From_F/items/6c97205fc20cd10a0ddf) 
　　[QR Codes Are Simple in Swift](https://medium.com/codex/qr-codes-are-simple-in-swift-6d203ebc3f5b)
(2) QRコードを読み取り
　　[Camera preview and a QR-code Scanner in SwiftUI](https://medium.com/dev-genius/camera-preview-and-a-qr-code-scanner-in-swiftui-48b111155c66)
　　[SwiftUIでAVFundationを導入する【Video Capture偏】](https://blog.personal-factory.com/2020/06/14/introduce-avfundation-by-swiftui/)
　　[【Swift】QRコードを読み取って文字列を取得する *UIKit](https://qiita.com/_asa08_/items/8562fe79ec6528a61b06)
(3) AVCapturePhotoOutputの実装例 *UIkit
　　[Swiftでカメラアプリを作成する(1)](https://qiita.com/t_okkan/items/f2ba9b7009b49fc2e30a)
　　[Swiftでカメラアプリを作成する(2)](https://qiita.com/t_okkan/items/b2dd11426eab107c5d15)
　　[[iPhone] AVCaptureVideoDataOutput ビデオで静止画撮影する](https://i-app-tec.com/ios/avcapturevideodataoutput.html)
　　[[iPhone] Camera撮影, AVCapturePhotoOutput](https://i-app-tec.com/ios/avcapturestillimageoutput.html)
(4) 『QRコード』の仕組みの説明。記録データの大きさや誤り訂正機能など
　　[『QRコード』の仕組みを解説！ 決済やスマホでの読み取り方・作成方法も](https://time-space.kddi.com/ict-keywords/20190425/2624)


## Xcode
(1) [Xcodeのリファクタリング機能の詳しい紹介](https://www.avanderlee.com/swift/xcode-refactoring/)
(2) [Xcodeの使い方](https://medium.com/swlh/my-top-5-xcode-tricks-9c4301a2118a)
(3) [Crash Report](https://medium.com/better-programming/4-tips-to-make-it-easier-to-fix-crashes-and-bugs-472f1f9c067d)
(4) [ドキュメントコメントの残し方まとめ](https://qiita.com/ashdik/items/0019b6ba5ed228d41e66)
(5) [Xcodeシミュレーター管理コマンド](https://qiita.com/MaShunzhe/items/14d95bb27eb3d51c4233)
(6) [Preview の表示設定](https://www.avanderlee.com/swiftui/previews-different-states/)
(7) [PlaygroundでSwiftUIのViewを描画する](https://iganin.hatenablog.com/entry/2020/05/18/065940)

## 基本構文
(1) [Swift演算子まとめ](https://qiita.com/KentaKudo/items/a1dd2eb0bf37302c1c81)
(2) [”append() vs. appending()” や ”isEmpty() vs. count == 0” など、類似した構文の使い分け](https://medium.com/better-programming/10-confusing-but-crucial-swift-functions-f290dfaaf-f29080bdfaaf)
(3) [scaledToFill()とかscaledToFit()とかaspectRatio(_:contentMode:)等の使い分け](https://qiita.com/Riscait/items/71a9492314b8424274f1)
(4) [zip, dump, sequence などの関数の使い方](https://medium.com/better-programming/useful-global-swift-functions-e028b1c2afa)
(5) [Swift: Understand Recursive Enum in Five Minutes](https://levelup.gitconnected.com/swift-understand-recursive-enum-in-five-minutes-d4aff8bd50bb)
(6) [lazyの使い方](https://ayusinghi96.medium.com/lazy-keyword-swift-5-ios-eae3dde18a80)
(7) [forEach, forIn, forEnumerated の使い方](https://medium.com/swift2go/foreach-forin-forenumerated-in-swift-c12552be72db)
(8) [Swift Attributes @discardableResult など](https://medium.com/geekculture/week-2-i-bet-that-you-dont-know-all-these-attributes-in-swift-99cbeb603594)
(9) [文字列の先頭・末尾の1文字を取得する方法](https://chusotsu-program.com/swift-string-index/)
(10) [Strong, weak, and unowned references — Swift 5 (iOS)](https://ayusinghi96.medium.com/referencing-in-swift-5bd1b3163ec)
(11) [10 Different Ways to Loop in Swift](https://betterprogramming.pub/10-different-ways-to-loop-in-swift-20bd5a40c8f7)
(12) [Swift Protocols With Associated Types and Generics](https://betterprogramming.pub/swift-protocols-with-associated-types-and-generics-373b2927baed)


#3. Tips
### ScrollViewとLongPressGestureを併用

  ```swift
  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](https://developer.apple.com/forums/thread/127277)
***

### NavigationBarの装飾

```swift
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 タイトルカスタマイズ方法](https://qiita.com/titanium/items/690b15c4471f4cd22516)
[Navigation Barのボーダーを削除する](https://qiita.com/narikei/items/f7ff7cc8792dab2ab2b3)

***

### TabViewの挙動

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

```swift: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)
        }
    }
}
```

```swift: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のテキストに設定される
    }
}
```

参考リンク
+ [SwiftUI - Change TabBar Icon Color](https://stackoverflow.com/questions/57109839/swiftui-change-tabbar-icon-color)
+ [SwiftUI - TabView initial tabItem not displaying text](https://stackoverflow.com/questions/62553055/swiftui-tabview-initial-tabitem-not-displaying-text)


***

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

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

```swift
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](https://medium.com/better-programming/swiftui-tips-and-tricks-c7840d8eb01b)
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335850/229a5660-7839-8c9a-95c6-b9234fa6b708.png)
In the code above, the RandomText no longer refreshes the text when the bottom sheet is presented or dismissed.
***

### NotificationCenterの購読 @SwiftUI

```swift:発行側（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)
   }
}
```

```swift:購読側（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の実装

```swift
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](https://stackoverflow.com/questions/56550135/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](https://www.appcoda.com/swiftui-border/)

***

### Leverage Frame Modifier for Filling Views

出典：[SwiftUI Tips and Tricks](https://medium.com/better-programming/swiftui-tips-and-tricks-c7840d8eb01b)
By default, views occupy minimal space on the screen. For instance, the following TextView wraps itself:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335850/0785024e-413c-225f-8d7e-10f6a2f50015.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335850/da9cc631-4746-34b0-8e62-94364fb235a0.png)
***

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

```swift
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を作成する場合は、こんな感じ。

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

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

```

### Modifierのスタイルをテンプレート化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335850/b99aaaa2-a9a7-cf8a-7199-548cf3e5ac70.png)

### Packageの編集

Video： [Creating Swift Packages](https://developer.apple.com/videos/play/wwdc2019/410/)
実装例： [Editing A Swift Package](https://useyourloaf.com/blog/editing-a-swift-package/)

（一部抜粋）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335850/8905c7d0-c7af-0da4-4efc-f047f0311f6b.png)