#1. 内容
アプリ開発のために調べた情報の中で、今後も参照する機会が多いと思うものをストックするものです。技術的な知見を惜しげもなく公開してくださる皆様のおかげでアプリを作れています。ありがとうございます
#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_ での source と destination の値について
(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
}
}
}
}
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の挙動
TabView と NavigationBar を組み合わせた際におかしな挙動があるので、注意。なお、 TabBarItem の装飾には、 accentColor モディファイアを使う
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)
}
}
}
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:
In the code above, the RandomText no longer refreshes the text when the bottom sheet is presented or dismissed.
NotificationCenterの購読 @SwiftUI
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)
}
}
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:
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:
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のスタイルをテンプレート化
Packageの編集
Video: Creating Swift Packages
実装例: Editing A Swift Package