見出し画像

Swiftで調べたことコピペ集

iOSアプリのプロトタイプを作ることになり、それがネイティブっぽいUIだったこともあって、いままでUnityでしか作ってなかったiOSアプリをSwiftで作ってみよかな、というのが始めるキッカケ。
Unityだとネイティブ機能使うのにライブラリ探したり(有料だと会社にお願いせなあかんのです)、まあ一度ネイティブ触ってた方が今後のためだなーと考えてたもので。

あと、らくがきARのAndroid対応が大変だったので(すみません、自分は担当してないですが、横から見てて)、普段iPhone使ってるし、まあAndroidアプリって自分から作ることないかな・・・というのがSwiftを触る気持ちをさらに後押ししたり。そんなこと書いていいのか。まあいいか。

教本からスタート

実装期間が始まる前に、まずこれを買った。
この本の通りに作ってみる。

・たった2日でマスターするiPhoneアプリ開発集中講座
https://www.amazon.co.jp/%E3%81%9F%E3%81%A3%E3%81%9F2%E6%97%A5%E3%81%A7%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%BC%E3%81%99%E3%82%8BiPhone%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E9%9B%86%E4%B8%AD%E8%AC%9B%E5%BA%A7-Xcode-11-Swift-5%E5%AF%BE%E5%BF%9C/dp/4802612362

全体の雰囲気は掴める。この時点で、わからないことが結構あるので、やりたいことをゴニョゴニョと箇条書きしてた。

・クラス分けての作り方
・複雑なUI周りの作り方(OS的じゃない動き)
・トゥイーン系のモーション付け方
・通信まわり
・各解像度対応
・デバッグまわりの方法

などなど。
この先、特に書いてないんだけど、AutoLayoutとか慣れない間は、どこに設定されてんねん!思った通りにいかん!二重でかかってる!もうちょいなんとかならんのか!!ということの連続なんだけど、続けてくとだんだん諦めがつく(納得はしてない)。

Line風アプリの実装

読んだ本が実践的に進めてく感じだったんだけど、も少し具体的にUI作るときのお作法みたいなものを知りたくて、Youtubeに上がっている動画を参考にしてみる。

Ep1-12まである。これを目標にして、たんたんとやることにした。
展開早すぎるけど、一緒に作りながら置いてかれたら巻き戻し。
Ep終わるごとに振り返って見直す。とりあえずこなして、さらに雰囲気を掴んでみる。途中から一緒に作業するの諦めて、みるだけになったけど。

Swift言語学習

LINE風アプリの作り方動画を真似したり眺めたりしつつ、ボチボチ自分でアプリ作り始めたぐらいで、なんとなく呪文っぽく書かされてたSwiftについて学ぶ。

・第3版 Swift実践入門
https://gihyo.jp/book/2020/978-4-297-11213-4

これ買って読んでみた。
普段Unity使っててC#を書いていると出てこない、特にエラーを避けるために言語実装されているオプショナルやアンラップ、nilまわりのことや、if letやguardなど基本的な構文でも全て上位版って感じで、なんとなく呪文的に書かされてたことがガーっと理解できる。
大体一緒だろうと思って読み飛ばせないぐらい色々あるので、読んどかないと後々人のコード訳わからないだろうなと思って一通り見つつ、スマートでお洒落な言語だなーという感想。
なによりXcodeの補完とかもう使われてないメソッドの代替とか提案してきて、エディタとにかく便利。いままで触ってきた中で一番快適かも。(Vimが標準で使えたらなあ・・新しいエディタにするとすぐvim入力に変える人)
あと、ネイティブの機能使ってるとやたらDelegateを使うことになるので慣れてくださいませ。

てなことをやりつつ

言語本を読み始めたあたりで実装期間が始まったので、あとは調べながら作っていくことに。その都度調べたことがEvernoteに溜まっているので、とにかく放出します。基本は他の人の記事リンクとそこにあるソースのコピペです。順番めちゃくちゃで雑多ですが、お暇な方はメモとしてみてやってください。
Xcode12.3、Swift5、Storyboardで実装してます。実機はiPhone12Pro。結構Swiftはバージョン違いで構文が変わってたりするので、コピペしても動かなくて、新しい記事をまた探す旅に出るみたいなのはあるあるな気がする。

※学ぶためにコピペしたものを貼ってるので、転載だらけですみません!!気悪いぞ!という方はおっしゃってください消しますー!!
&コピペしてるけど、コピペ元のリンクがなかった場合、自分のメモミスです。ほんとにすみません。すみませんすみませーーん!
&本当は違うページ見て学んだのにリンクは違うとこ貼ってたりしてるかも!知らんがな!これキッカケに自分で学んで!!!!

アプリ内のデータの見方

iPhoneローカルに保存したファイルを確認したいときに、Xcodeからデータを引き抜く方法。

・iOSでアプリ内のデータってどうやってみるの?
https://qiita.com/nambara/items/bc0649b5c2d955bff220

メモリリークの確認方法

・[iOS] メモリリークをXcodeでチェックして、リークしないようにしたい!
https://dev.classmethod.jp/articles/ios-memory-leak-check-and-prevent-190508/

CocoaPods

CocoaPodsは、Swiftのライブラリ管理。

・CocoaPods
https://cocoapods.org/

プロジェクトのディレクトリに移動した後にpod init

pod init
open podfile

podfileファイルを開いてインストールリストを更新

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'PodTestApp' do
    # Comment the next line if you don't want to use dynamic frameworks
    use_frameworks!
    
    # Pods for PodTestApp
    pod 'PKHUD', '~> 5.0'

end

こんな感じで入れる。pod なんちゃら は、入れたいライブラリのGithubやらにだいたい書いてる。最後にinstall。

pod install

これで入るので、importを書くと補完で出てくる。
なるほど。簡単。

便利ライブラリまとめ

CocoaPods覚えたので、Swift使っていくにあたって、どんなライブラリがよく使われているのか調査。

・【Swift】iOSアプリ開発で使える(使いたい)Swiftライブラリー
https://qiita.com/BlueEventHorizon/items/080f1e24a6c3d76a0df8
・iOSライブラリ選定チートシート
https://qiita.com/kntkymt/items/ad0ec4da3f510b885bea

画像の取得やキャッシュ

今回はNukeを使った。

---- NSCashe ----

・【swift4】 imageをcache化する
https://qiita.com/yasui83/items/73c31ddb72337e674fe3

---- Nuke ----

取得もキャッシュもできて便利そう。
https://github.com/kean/Nuke

・Nuke + UIImageViewでいい感じにURLを読み込ませたい!
https://tech.studyplus.co.jp/entry/2020/02/10/101801
・Swiftの有名画像キャッシュライブラリを比較してみた
https://qiita.com/hcrane/items/422811dfc18ae919f8a4

非同期処理

結局、非同期処理のライブラリは使わなかった。リンクだけ。

---- PromiseKit ----

一番有名っぽい。
https://github.com/mxcl/PromiseKit

---- Promises ----

Google製らしい
https://github.com/google/promises

---- RxSwift ----

おなじみ
https://github.com/ReactiveX/RxSwift

API通信処理(Alamofire)

---- Alamofire ----

Alamofireというライブラリが定番らしい。
https://github.com/Alamofire/Alamofire

・標準とAlamofireでAPIコール処理を書き比べてみる(Swift)
https://qiita.com/uhooi/items/c0e083f916dc516175bd

うん、使いやすそう。

pod 'Alamofire', '~> 5.2'

---- Codable ----

json文字列のデコーダ。swift標準。

・【Swift】Codableについて備忘録
https://qiita.com/s_emoto/items/deda5abcb0adc2217e86

---- Alamofire使い方 ----

コピペしてもエラー出るなと思ったら、クラス名が AF になってた。
https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md

以下で動いた。オッケー。

import Alamofire
import UIKit
class ViewController: UIViewController
{
   private var addresses: AddressModel?
   override func viewDidLoad() {
       super.viewDidLoad()
       getAddress(zipCode: "7000055" )
   }
   
   private func getAddress(zipCode: String)
   {
       let baseUrl = "https://zipcloud.ibsnet.co.jp/api/"
       let searchUrl = "\(baseUrl)search"
       let parameters: [String: Any] = ["zipcode": zipCode]
       let headers: HTTPHeaders = ["Content-Type": "application/json"]
       
       AF.request( searchUrl, method: .get, parameters: parameters, encoding: URLEncoding( destination: .queryString ), headers: headers ).responseJSON
       {
           response in
               guard let data = response.data else
               {
                   return
               }
           
               do
               {
                   self.addresses = try JSONDecoder().decode(AddressModel.self, from: data)
                   print( self.addresses?.results[0].address1 )
               }
               catch let error
               {
                   print("Error: \(error)")
               }
       }
       
   }
}
struct AddressModel: Decodable {
   var results: [Result]
   struct Result: Decodable {
       var address1: String
       var address2: String
       var address3: String
       var kana1: String
       var kana2: String
       var kana3: String
   }
}

---- POSTでm4aファイルを送信する ----

・Sending recorded audio file to server by using Alamofire 5 beta newest Version
https://forums.raywenderlich.com/t/sending-recorded-audio-file-to-server-by-using-alamofire-5-beta-newest-version/79754
//
// ローカル保存した音声ファイルのURLを取得
let dataURL = URL( fileURLWithPath: voice.voiceDataPath )
do
{
   //
   // ファイル読み込み
   let binaryData = try Data( contentsOf: dataURL, options: [] )
   
   AF.upload( multipartFormData:
                   {
                       (multipartFormData) in
                           multipartFormData.append( binaryData, withName: "audio", fileName: dataURL.lastPathComponent, mimeType: "audio/m4a" )
                   }, to: url ).responseJSON
   {
       ( response ) in
           debugPrint( response )
   }
   
}
catch
{
   print( "音声データのバイナリ化に失敗しました" )
   return
}

AF.uploadまわり。大きいデータをアップロードするあたりの説明
https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server

---- http:// のサーバーに通信するとエラーが出る問題 ----

postでAPI叩く処理を実行すると、こんなエラーが出た。
httpsじゃないURLにアクセスできない設定らしい。

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
・「http://」のAPIを実行できるようにする(Swift)
https://qiita.com/uhooi/items/68939999c2c31e5f5557

とりあえず今はモックなので、Info.plistのApp Transport Security Settings > Allow Arbitrary Loads の値を YES に指定することにした。

---- download で音声ファイルをローカルに保存する ----

・Alamofire Tutorial with Swift (Quickstart)
https://codewithchris.com/alamofire/
public func downloadVoice( filePath : String )
{
   let url = URL(string: filePath)
   let fileName : String = url!.lastPathComponent
   
   let destination: DownloadRequest.Destination = { _, _ in
               let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
               let fileURL = documentsURL.appendingPathComponent( fileName )
               return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
          }
   
   AF.download( filePath, to: destination ).response
   {
       response in
       if response.error == nil, let imagePath = response.fileURL?.path
       {
           // DL成功
           print( "success - audio download" )
           print( "audioPath : \(imagePath)" )
           
       }
       else
       {
          // DL失敗
          print("error - audio download")
       }
   }
}

UIImageをbase64エンコード

・Swift UIImageデータをbase64エンコードする
https://www.letitride.jp/entry/2019/08/16/134412

---- エンコード ----

let imageData = image.pngData()! as NSData
// for jpg
//let imageData = image.jpegData(compressionQuality: 0.8)! as NSData
let base64String = imageData.base64EncodedString(options: .lineLength64Characters)

---- デコード ----

let imageData = NSData(base64Encoded: base64String, options: .ignoreUnknownCharacters)
let image = UIImage(data: imageData! as Data)

ローカルにデータ保存

・【iOS】デバイス(ローカル)にデータを保存する方法
https://qiita.com/shiz/items/c7a9b3218269c5c92fed
UserDefaults.standard.set(true, forKey: "isLoggedIn")
let isLoggedIn = UserDefaults.standard.bool(forKey: "isLoggedIn")

オーディオ処理

ゴニョゴニョ書いてたけど、こんなライブラリもあった。(使わなかった)
https://github.com/AudioKit/AudioKit

・[Swift4] バックグラウンドでオーディオ再生する
https://qiita.com/kenny_J_7/items/936d91151149868618a8

---- 別音声ファイルを連続して再生 ----

AVAudioPlayerNodeなど使って、別音声ファイルを連続して再生する場合、
ScheduleFile()を使うんだけど、再生完了時に再度再生する再帰的な処理を書いたらエラーで続けて心折れそうになった。
結論、スレッドが違ったらしい。メインスレッドで処理しないといけなかった。DispatchQueue.main.async をかけばメインスレッド実行になる

public func play( url:URL, isAllPlay:Bool = false )
{
   _audioEngine = AVAudioEngine()
   do
   {
       _audioFile = try AVAudioFile(forReading: url)
       _audioPlayerNode = AVAudioPlayerNode()
       
       _audioEngine.attach(_audioPlayerNode)
       _audioEngine.connect(_audioPlayerNode, to: _audioEngine.outputNode, format: _audioFile.processingFormat)
       
       _audioPlayerNode.scheduleFile(_audioFile, at: nil, completionCallbackType: .dataPlayedBack)
                                       {
                                           _ in
                                               DispatchQueue.main.async {
                                                   self.delegate?.onCompleted()
                                               }
                                       }
                                     
       
       try _audioEngine.start()
       _audioPlayerNode.play()
       
       
   }
   catch let error
   {
       print(error)
   }
}
・completionHandler of AVAudioPlayerNode.scheduleFile() is called too early
https://stackoverflow.com/questions/29427253/completionhandler-of-avaudioplayernode-schedulefile-is-called-too-early
・Swift4でDispatchQueueを使う
https://qiita.com/lumbermill/items/7553a6ace26019f08bc6

---- 録音した音声が小さい ----

再生するときの、_audioPlayerNode.volume=1.0が効いてないのが原因と思って調べていたが、そこじゃなくて、AVAudioSessionの設定にoptions: .defaultToSpeaker が入ってないだけだった。

private init()
{
   let session = AVAudioSession.sharedInstance()
   do
   {
       try session.setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker)
       try session.setActive(true)
   }
   catch let error
   {
       print(error)
   }
}
・録音した音声が小さく、原因が分かりません。
https://teratail.com/questions/241435

---- 再生速度を変える ----

AVAudioUnitTimePitch、AVAudioUnitVarispeedをAVAudioEngineにattach&connectすることで使用できる。
AVAudioUnitTimePitchのrateが再生スピード。pitchがピッチ。
AVAudioUnitVarispeedのrateが再生スピードとピッチを同期して変化するエフェクト。なのでspeed側だけいじれば再生速度はいい感じに変化できるっぽい。

_audioEngine = AVAudioEngine()
do
{
   _audioFile = try AVAudioFile(forReading: url)
   _audioPlayerNode = AVAudioPlayerNode()
   _audioSpeedControl = AVAudioUnitVarispeed()
   _audioPitchControl = AVAudioUnitTimePitch()

   _audioEngine.attach(_audioPlayerNode)
   _audioEngine.attach(_audioPitchControl)
   _audioEngine.attach(_audioSpeedControl)

   _audioEngine.connect(_audioPlayerNode, to: _audioSpeedControl, format: nil)
   _audioEngine.connect(_audioSpeedControl, to: _audioPitchControl, format: nil)
   _audioEngine.connect(_audioPitchControl, to: _audioEngine.mainMixerNode, format: nil)
   
   _audioPitchControl.rate = pitchRate // Range: 1/32 -> 32.0
   _audioPitchControl.pitch = pitch    // Range: -2400 -> 2400
   _audioSpeedControl.rate = speedRate // Range: 0.25 -> 4.0
   ...
}
・AVAudioPlayerNodeを使って音楽の再生、一時停止、再生速度変更、ピッチ変更、ボリューム変更を行う
https://swiswiswift.com/2020-12-09/
・AVAudioUnitのパラメータ詳細(基本編)
https://qiita.com/MJeeeey/items/e5dc75b20adb59c7d48f

リアルタイム音声認識

・Swiftでリアルタイム音声認識するための最小コード
https://qiita.com/mishimay/items/71304f0aa2a313ad93ac

テーブルUI

・初めてでも分かる!カスタムセルをSwiftで使用する方法
https://yuu.1000quu.com/use_a_custom_cell_in_swift
class ViewController: UIViewController

{
   @IBOutlet weak var userTable: UITableView!
   
   override func viewDidLoad()
   {
       super.viewDidLoad()
       
       userTable.dataSource = self
       userTable.delegate = self
       userTable.tableFooterView = UIView(frame: .zero)
   }
   
   override func viewWillAppear(_ animated: Bool)
   {
       super.viewWillAppear(animated)
   }
}
extension ViewController: UITableViewDataSource, UITableViewDelegate
{
   override func didReceiveMemoryWarning()
   {
       super.didReceiveMemoryWarning()
   }
   
   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
   {
       return 5
   }
   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
   {
       return 60
   }
   
   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
   {
       let cell = tableView.dequeueReusableCell(withIdentifier: "userCell", for: indexPath)
       cell.textLabel!.text = "hoge"
       return cell
   }
   
   func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
   {
       //performSegue(withIdentifier: _menuList[ indexPath.row ].1, sender: nil)
   }
}

---- 横スライドでセル削除 ----

・TableViewを編集してCellを削除する(横スライド)
https://swiswiswift.com/2018-04-23/
・swift4 - スワイプでtableviewのセル削除
https://qiita.com/Lulu34/items/b0c88d1e1163d50f743b
・削除ラベルのタイトルを変える
https://blog.mothule.com/ios/uitableview/ios-uitableview-uitableviewcell-edit-mode
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
   return "削除する"
}

横スライドCollectionView

・UICollectionViewでページングスクロールを実装する
https://techlife.cookpad.com/entry/2019/08/16/090000

横スライドビューは、UICollectionView を使うらしい。

・UICollectionViewで画像を横スクロールする方法
https://qiita.com/r0227n/items/3dc3540b61c22f0c46d9

ここが最小構成。テーブルとほぼ同じ感覚。
スライドで、指離したあと絶妙な位置に動かしたい。

・【Swift4】UICollectionViewを使ってカルーセルを実装してみた。【ページング編】
https://uruly.xyz/carousel-infinite-scroll-3/

collectionViewを使ったページングに正解がないらしい。。マジか。
自分で実装しないといけない。マジで。はあ。

UICollectionViewFlowLayoutのサブクラスを作って、XCode上でアタッチする。
override func targetContentOffset() で、指が離れた時のイベントが取れるので、そのタイミングのoffsetから画面中央から一番近いcellを見つけて、cellの中央のxを返す。すると自動で動く。

今度はスクリプトからcollectionViewを動かしたい。Layoutまわりの値を入れる方法を考えていたが、CollectionView.setContentOffsetで動いた。

・setContentOffset(_:animated:)
https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset?language=swift

CGPointで指定するので、section&rowからCGPointを返すメソッドが必要。

UIScrollView

ググればググるほど、XCodeのUIScrollViewはわかりにくいと出てくる。
たしかにわかりにくかった。

・【Xcode11】いつもスクロールしなかったUIScrollView + AutoLayoutをやっと攻略できた
https://swallow-incubate.com/archives/blog/20200805
ScrollView配置。AutoLayoutで上下左右0。
ScrollViewにUIView配置。ContentViewを配置したら、ScrollViewのContent Layout Guideに制約を設定。
ContentViewとContent Layout Guideの両方を選択して、Add New Alignment Constraintsをクリックして下記の4つの制約にチェックをつけて、Add 4 Constraintsをクリックして制約を設定。こちらも全て0で設定。
- Leading Edges
- Trailing Edges
- Top Edges
- BottomEdges

ctrl キーを押しながら、ContentViewからFrame Layout Guideにドラッグ & ドロップをして、Equal Widthをクリック。
注意点としてはContentViewのWidthがScrollViewのWidthに設定されることになるので、ContentViewの横幅を先に画面いっぱいに広げておいた方が良さそうです。

最後にContentViewに高さを設定します。今回はとりあえず1000を設定しておきます。はい、ここまでやったらようやくエラーが消えていると思います。​
・UIScrollView
https://seeking-star.com/prog/swift/uiscrollview/
・よく使うデリゲートのテンプレート
https://qiita.com/hoshi005/items/92771d82857e08460e5c
・UIScrollViewで常にBouncesを有効にする
https://tech-blog.sgr-ksmt.org/2016/03/11/always_bounces/
scrollView.alwaysBounceVertical = true // 縦方向に関して常にバウンスする
scrollView.alwaysBounceHorizontal = true // 横方向に関して常にバウンスする

UIView装飾

---- ドロップシャドウ ----

・【Swift】Viewへの影を自在に操る
https://tech.playground.style/swift/view-shadow/
self.layer.shadowOffset = CGSize(width: 0.0, height: 4.0)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 3

UINavigationController

・UINavigationControllerで2つ以上前の画面に戻る方法
https://qiita.com/k-yamada-github/items/65b8a8cb17fd1912aca5
// 2つ前のViewControllerに戻る
let index = navigationController!.viewControllers.count - 3
navigationController?.popToViewController(navigationController!.viewControllers[index], animated: true)

Segueでデータを受け渡す

・【Swift】画面遷移しながら値を渡す方法
https://nekokichi2yos2.hatenablog.com/entry/2018/10/13/233049

segueを指定して移動するタイミングと、
データを受け渡す箇所が離れているので注意。

//
// ボタン押す
@IBAction func first(_ sender: Any) 
{
   handOver("first")
   performSegue(withIdentifier: "perform", sender: nil)
}
//
// 遷移する際の処理
override func prepare(for segue: UIStoryboardSegue, sender: Any?) 
{
   if segue.identifier == "perform" 
   {
       let svc = segue.destination as! SecondViewController
       svc.text = text
   }
}

スワイプジェスチャー

・【Swift/UIKit】タップ・長押し・スワイプを認識させる
https://hirakana.hatenablog.jp/entry/2020/01/31/212238
//右へ
let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swiped(_:)))
rightSwipeGesture.direction = .right
//左へ
let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swiped(_:)))
leftSwipeGesture.direction = .left
//上へ
let upSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swiped(_:)))
upSwipeGesture.direction = .up
//下へ
let downSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swiped(_:)))
downSwipeGesture.direction = .down

view.addGestureRecognizer(rightSwipeGesture)
view.addGestureRecognizer(leftSwipeGesture)
view.addGestureRecognizer(upSwipeGesture)
view.addGestureRecognizer(downSwipeGesture)

@objc func swiped(_ sender: UISwipeGestureRecognizer) {
   switch sender.direction {
   case .left:
       print("swiped left")
   case .right:
       print("swiped right")
   case .up:
       print("swiped up")
   case .down:
       print("swiped down")
   default:
       break
   }
}

Stringいろいろ

・DataとStringの相互変換
https://qiita.com/yuki-k/items/3c1f60a2633a2c6e712f
let data: Data? = str.data(using: .utf8)
let str: String? = String(data: data, encoding: .utf8)
・Swiftで半角空白文字削除(trim)
https://unokun.hatenablog.jp/entry/2018/04/08/140747
let myString = "  \t\t  Let's trim all the whitespace  \n \t  \n  "
let trimmedString = myString.trimmingCharacters(in: .whitespacesAndNewlines)
print(myString)
・Codableで色々なJSONに対応する
https://qiita.com/Mt-Hodaka/items/d14447a429948a3fb28c
let devices = try? JSONDecoder().decode([Device].self, from: list)

テキストフィールド

・【Swift】UITextFiledの使い方、キーボード表示、閉じるDelegate
https://pg-happy.jp/swift-uitextfield.html
・テキストフィールド編集でのイベントハンドリング – UITextFieldDelegate
https://www.ukeyslabo.com/development/iosapplication/swift/uitextfielddelegate/
・Swiftでテキストフィールド(UITextField)をコードで追加する
https://majintools.com/2018/10/17/textfield/

プロトコル

プロトコルは、C#でいうところのInterface。

・【Swift入門】プロトコル(protocol)を使ってみよう!
https://www.sejuku.net/blog/35550
protocol プロトコル名 {
	var プロパティ名: 型 { set get }
	func メソッド名(引数名: 型) -> 戻り値の型
}

クラスの判定

・【Swift4】クラスの判定(型を比較)する方法【Objective-C】
https://program-life.com/442

---- is で判別 ----

継承元のクラスも許可する場合

let label = UILabel()
       
if label is UIView {
   print("一致")
}
if label is UILabel {
   print("一致")
}

---- type of で判別 ----

こちらは型一致で許可したい場合

let label = UILabel()
if type(of: label) != UIView.self {
   print("不一致")
}
if type(of: label) == UILabel.self {
   print("一致")
}

バックグラウンド、フォアグラウンド

・【Swift】iOSアプリがフォアグラウンドになった時に、更新処理をさせてみたい!
https://qiita.com/mushroominger/items/b03860d8a0a746dcd164
import UIKit
class ViewController: UIViewController {
   override func viewDidLoad() {
       super.viewDidLoad()
       NotificationCenter.default.addObserver(
           self,
           selector: #selector(ViewController.viewWillEnterForeground(_:)),
           name: UIApplication.willEnterForegroundNotification,
           object: nil)
       NotificationCenter.default.addObserver(
           self,
           selector: #selector(ViewController.viewDidEnterBackground(_:)),
           name: UIApplication.didEnterBackgroundNotification,
           object: nil)
   }
   @objc func viewWillEnterForeground(_ notification: Notification?) {
       if (self.isViewLoaded && (self.view.window != nil)) {
           print("フォアグラウンド")
       }
   }
   @objc func viewDidEnterBackground(_ notification: Notification?) {
       if (self.isViewLoaded && (self.view.window != nil)) {
           print("バックグラウンド")
       }
   }
}

画像選択、カメラ撮影

アルバムから画像選択orカメラ撮影をして、その画像をUIImageに設定する

---- 画像選択 ----

・iPhone本体からアプリに画像を読み込む
https://note.com/nyakko/n/n91d19d97610c

UIImagePickerControllerで画像選択UI出す。
delegateにselfを指定して、extensionで選択した時とキャンセルした時の処理を設定する。

//
// 選択画像
@IBOutlet weak var voiceVisualPreview: UIImageView!
//
// 写真を添付ボタン
@IBAction func addVisualBtn(_ sender: Any)
{
   let picker = UIImagePickerController()
   picker.sourceType = .photoLibrary
   picker.delegate = self
   present(picker, animated: true)
   self.present(picker, animated: true, completion: nil)
}
//
// 画像アルバムから選択
extension RecVoiceViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
   //
   // 画像が選択された時に呼ばれる
   func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
   {
      if let selectedImage = info[.originalImage] as? UIImage
      {
           voiceVisualPreview.image = selectedImage
      }
      self.dismiss(animated: true)
   }
   //
   // 画像選択がキャンセルされた時に呼ばれる
   func imagePickerControllerDidCancel(_ picker: UIImagePickerController)
   {
      self.dismiss(animated: true, completion: nil)
   }
}

---- カメラ撮影 ----

・[iPhone] UIImagePickerController による Camera撮影
https://i-app-tec.com/ios/camera.html
// カメラが利用可能かチェック
if UIImagePickerController.isSourceTypeAvailable(.camera)
{
   let picker = UIImagePickerController()
   picker.sourceType = .camera
   picker.delegate = self
   self.present(picker, animated: true, completion: nil)
}
else
{
   print("error")
}

picker.sourceType = .camera
にするだけで、カメラ撮影になる。

---- 画像選択とカメラ撮影を選ばせる ----

・swift5でダイアログを表示する方法
https://qiita.com/kaneko77/items/010c3836a1a063ad015e

アラートで選択させればいい。
スタイルを .actionSheet にしたら、下からせりあがる選択になる。

let actionSheet = UIAlertController(title: "Menu", message: "", preferredStyle: UIAlertController.Style.actionSheet)
let action1 = UIAlertAction(title: "表示させたいタイトル1", style: UIAlertAction.Style.default, handler: {
   (action: UIAlertAction!) in
   //実際の処理
   print("表示させたいタイトル1の処理")
})
let action2 = UIAlertAction(title: "表示させたいタイトル2", style: UIAlertAction.Style.default, handler: {
   (action: UIAlertAction!) in
   //実際の処理
   print("表示させたいタイトル2の処理")
})
let close = UIAlertAction(title: "閉じる", style: UIAlertAction.Style.destructive, handler: {
   (action: UIAlertAction!) in
   //実際の処理
   print("閉じる")
})
actionSheet.addAction(action1)
actionSheet.addAction(action2)
actionSheet.addAction(close)
self.present(actionSheet, animated: true, completion: nil)

ローカルでプッシュ通知

・[swift5]iOSアプリでローカル通知を実装
https://qiita.com/jpmos7/items/3f2d5e14b74a239e1882
・猿でも分かるプッシュ通知
https://gist.github.com/pine/8b7e58ff7fa259df17351d959c6118f7
・【Swift】この時期だから見直すiOS10の新機能 UserNotificationsとNotification Content ExtensionとNotification Service Extension
https://qiita.com/shiz/items/ab39faae1ef3a758fc08
・iOS 10 での通知処理について
https://qiita.com/s-harada/items/8e82a7e9a2570bf25f4d#%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%80%9A%E7%9F%A5

AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate {
   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       // Override point for customization after application launch.
       // 通知許可の取得
       UNUserNotificationCenter.current().requestAuthorization(
       options: [.alert, .sound, .badge]){
           (granted, _) in
           if granted{
               UNUserNotificationCenter.current().delegate = self
           }
       }
       return true
   }
   ...
}
extension AppDelegate: UNUserNotificationCenterDelegate {
   func userNotificationCenter(
       _ center: UNUserNotificationCenter,
       willPresent notification: UNNotification,
       withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
   {
       // アプリ起動時も通知を行う
       completionHandler([ .badge, .sound, .alert ])
   }
}

ViewController

let content = UNMutableNotificationContent()
content.title = "お知らせ"
content.body = "ボタンを押しました。"
content.sound = UNNotificationSound.default
// 直ぐに通知を表示
let request = UNNotificationRequest(identifier: "immediately", content: content, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

AWS SNS で iOSプッシュ通知

・[2019年度版]AWS SNSでiOSのプッシュ通知 設定手順まとめ
https://kahoo.blog/howto-aws-sns-ios-push-notification/

流れはここでわかる。

証明書・プロビジョニングファイル・P12ファイルの作成
- AppIDの登録
- APNsの証明書作成
- P12ファイルの書き出し
- Provisioning Profileの作成

AWSでアプリケーションの作成

サンプルアプリでデバイストークン取得
- サンプルアプリのソース取得
- サンプルアプリのビルドと実行

プッシュ通知の送信テスト
- デバイストークンの登録
- プッシュ通知の送信

・iOS13におけるプッシュ通知に必要なデバイストークンの取得方法
https://grandbig.github.io/blog/2019/09/28/ios-devicetoken-2/

プッシュ通知を許可した場合に得られるデバイストークンの取得方法
AppDelegate.swift に書かないといけない。

class AppDelegate: UIResponder, UIApplicationDelegate 
{
   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       // Override point for customization after application launch.
       
       // ① プッシュ通知の利用許可のリクエスト送信
       UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
           guard granted else { return }
           DispatchQueue.main.async
           {
               // ② プッシュ通知利用の登録
               UIApplication.shared.registerForRemoteNotifications()
           }
       }
       
       return true
   }
   ...
}
extension AppDelegate
{
   // ③ プッシュ通知の利用登録が成功した場合
   func application(_ application: UIApplication,
                    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
   {
       let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
       print("Device token: \(token)")
   }
   // ④ プッシュ通知の利用登録が失敗した場合
   func application(_ application: UIApplication,
                    didFailToRegisterForRemoteNotificationsWithError error: Error)
   {
       print("Failed to register to APNs: \(error)")
   }
}

デバイスプレビューではトークンは取れない。
実機でテストすると出てくる。よしよし。

AppDelegateからViewControllerにデータを送る

プッシュ通知のデバイストークンを取得したあと、ViewControllerのメソッドからAPIをたたいてトークンを送りたいと思った時に、AppDelegate -> ViewControllerへのデータ受け渡しに悩んだ。

・[Swift] AppDelegate のイベントを取得する
http://s-prism3.seesaa.net/article/434874814.html

なんとかして今表示してるViewControllerを取得しようとしていたが、
そうじゃなくてNotification.default.postを使えばOKらしい。なるほど。
上は記述が古いので、下のリンクへ。

・[Swift 5]NotificationCenterでデータ(値)の渡し方
https://qiita.com/Cyemang/items/94f3ddc26649c797a3e4

通知名を追加

extension Notification.Name {
  static let hogeName = Notification.Name("hogeName")
}

通知送信

NotificationCenter.default.post(name: .hogeName, object: nil, userInfo: ["fugaData": "hogehoge"])

通知受信

override func viewDidLoad() {
  super.viewDidLoad()
  //受信設定
  NotificationCenter.default.addObserver(self, selector: #selector(piyoFunc(notification:)), name: .hogeName, object: nil)
}
@objc func piyoFunc(notification: NSNotification?) {
  let data = notification?.userInfo!["fugaData"]
  print(data) // hogehoge が出力される
}

バッジ消す

・アプリアイコンのBageと通知センター表示について
https://qiita.com/sachiko-kame/items/8f3b47812e2ad91470f8
UIApplication.shared.applicationIconBadgeNumber = 0

ダイアログ表示

・swift5でダイアログを表示する方法
https://qiita.com/kaneko77/items/010c3836a1a063ad015e

---- 選択なしアラート ----

//アラートのタイトル
let dialog = UIAlertController(title: "タイトル", message: "サブタイトル", preferredStyle: .alert)
//ボタンのタイトル
dialog.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
//実際に表示させる
self.present(dialog, animated: true, completion: nil)

ダークモード対応

・XCode11のシミュレーターでiOS13のダークモードを設定する方法
https://qiita.com/p_on_ro/items/91e6659fda662fb2aac0
・実践 iOS13ダークモード対応
https://qiita.com/hirothings/items/4834481d170332e173f5

ダークモードにしたら文字とか消えちゃうみたいなことがあることを知った。意外とダークモードにしてる人多い。  

TestFlightのやり方

・iOSのTestFlightを使って作ったアプリをテスターに送ってテストしてみましょう。
https://dev-yakuza.posstree.com/react-native/ios-testflight/
・[TestFlight][新機能] リンクをシェアするだけでアプリのβ配信・テストが出来ちゃう TestFlight Public Link を試してみた
https://dev.classmethod.jp/articles/how-to-distribute-app-with-testflight-public-link/

AppStoreConnectで新規アプリ作成
XCodeからビルドをアップロード
TestFlightタブからビルドを申請
輸出に関する事項に答えてビルドに関するテキストを入れると申請中になる。数時間で通った。

ということで

案件が終盤になってくると眉間にしわを寄せながら、近寄りがたい雰囲気で作業してるので、そういうときはだいたいEvernoteにメモするの忘れてるんですよね。そういうときに調べてることが大事だったりするんだけど。ほかいろいろあった気がするけど、まあいいや。

StoryBoardの使い方とか、Segueとか、AutoLayoutとか、ここに書いてる以外にもっと使ってる機能もあるんだけど、しょっちゅう使う機能は体で覚えてくださいませ。

ここ数ヶ月触ってて、UIさくっと作れて、エディタも言語もなんだか好きになって、すっかりSwift楽しいなーという気持ちで過ごしております。
案件はほぼ終わってしまったのですが、iOSアプリじゃなくても、制作で使うちょっとした便利ツールなんかもSwiftで書くのが快適だなと思ってます。
Flash -> Unityと触ってきましたが、今後はSwiftも使っていきたいと思っている今日この頃なのでした。コピペ疲れ。