Swift Code集

ちょっとしたコードや設定などですが、忘れがちであらためて調べるのも面倒なので、忘備録的なものです。
順不同で、気づいたものから順にSwift4で使えるコードを載せていきます。

## Documentsまでのパス取得
ドキュメントパスはインストールされるたびに変更になるため必ずこの関数で取得する

```Swift:Swift
// /Documentsまでのパス取得
    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
    let liblaryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0] as String
```

##設定の読み書き
・userDefaultsへの読み書き
・Keyを定義しておくこと
・UIColorを保存するためのextension

```Swift:
    let KEY_A_NAME = "Key_A_Name"
    let userDefaults = UserDefaults.standard

    // read 
    let Key_A = userDefaults.string(forKey: KEY_A_NAME)
    // write
    userDefaults.set(Name_A, forKey: KEY_A_NAME)

extension UserDefaults {
    
    func colorForKey(_ key: String) -> UIColor? {
        var color: UIColor?
        if let colorData = data(forKey: key) {
            color = NSKeyedUnarchiver.unarchiveObject(with: colorData) as? UIColor
        }
        return color
    }
    
    func setColor(_ color: UIColor?, forKey key: String) {
        var colorData: Data?
        if let color = color {
            colorData = NSKeyedArchiver.archivedData(withRootObject: color)
        }
        set(colorData, forKey: key)
    }
    
}


```

##日付の読み出し
ローカルな日付をYYYYMMDD形式の文字列で読み出す

```Swift:Swift
    func getLocalDateTimeString(_ date:Date? = Date() )->String {
        if date == nil {return "----/--/--"}
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale.autoupdatingCurrent
        dateFormatter.dateFormat = "yyyy/MM/dd"
        return dateFormatter.string(from: date!) as String
    }
```

おまけ：年月日時分秒のプリフィクスを作る

```Swift:Swift
    func createFileID()->String {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale.autoupdatingCurrent
        dateFormatter.dateFormat = "yyyyMMddHHmmss_"
        let dateString =  dateFormatter.string(from: Date()) as String
        return dateString
    }
```

##Jsonの読み出し
jsonDecoderでJsonファイルのデータを構造体に読み出し
・Codableな構造体を定義しておくこと
・おまけ：texts配列をCSVに変換する関数texts2CSV()
・初期状態では読み出し元ファイルがないのでデフォルトの設定から作る
・Audioフォルダに音声ファイルを管理するJsonの例

```Swift:Swift
struct Entry: Codable {
    var date:Date
    var title:String
    var memo:String
    var audioFilePath:String
    var audioFileName:String
    var texts:[TextPhrase]
    
    /// カンマ区切りの文字列に変換
    func texts2CSV() -> String {
        var rtnStr = ""
        for i in 0 ..< texts.count {
            let time:String = "\"" + texts[i].time2hhmmss() + "\",\""
            let speaker:String = texts[i].speaker  + "\",\""
            rtnStr += time + speaker + texts[i].text  + "\"\r\n"
        }
        return rtnStr
    }
}

struct TextPhrase: Codable {
    var top:TimeInterval
    var end:TimeInterval?
    var speaker:String
    var text:String
    var comment:String
    
    func time2hhmmss() -> String {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .positional
        formatter.allowedUnits = [.minute,.hour,.second]
        formatter.zeroFormattingBehavior = .pad
        return formatter.string(from: top) ?? ""
    }
}
    var entries: [Entry] = []

    func readSavedJson() {
        let jsonPath = liblaryPath + "/" + jsonFileName
        // jasonをファイルから読み出す
        let fm = FileManager()
        let jsonDecoder = JSONDecoder()
        let jsonStr:String


        // ファイルの有無をチェック
        if fm.fileExists(atPath: jsonPath) {
            do {
                jsonStr = try String(contentsOfFile: jsonPath, encoding: .utf8)
            } catch _ {
                return
            }
        } else {
            print("No File : \(jsonPath)")
            //entries = []
            // 初期状態では何もないのでリソースから作る
            if let assetJson = NSDataAsset(name: "originJson") {
                jsonStr = String(data: assetJson.data, encoding: .utf8) ?? ""
                // サンプルファイルをAudioフォルダにコピー
                if let assetAudio = NSDataAsset(name: "sampleAudio") {
                    let audioData = assetAudio.data as NSData
                    let audioFilePath = documentsPath + "/Audio/sample.mp3"
                    let audioFolderPath = documentsPath + "/Audio"
                    //Audioフォルダが無ければ作る
                    var isDirExists : ObjCBool = false
                    fm.fileExists(atPath: audioFolderPath, isDirectory:&isDirExists)
                    if !isDirExists.boolValue {
                        do {
                            try fm.createDirectory(atPath: audioFolderPath, withIntermediateDirectories: true, attributes: nil)
                        } catch let error1 {
                            print("Error createDirectory : \(audioFolderPath) : " + error1.localizedDescription  )
                            return
                        }
                    }
                    // ファイルを書き込む
                    audioData.write(toFile: audioFilePath, atomically: true)
                }
                
            } else {
                entries = [Entry(date:Date(),title:"No Date",memo:"",audioFilePath:"NoAudio.mp4",audioFileName:"NoAudio.mp4",texts:[TextPhrase(top:0,end:nil,speaker:"No One",text:"say something",comment:"no comment")])]
                return
            }
        }
        
        entries = try! jsonDecoder.decode([Entry].self, from: jsonStr.data(using: .utf8)!)

    }
```

読み書きするjsonはこんな感じ

```json:json
[
  {
    "memo" : "",
    "title" : "このアプリの使い方",
    "audioFileName" : "sample.mp3",
    "texts" : [
      {
        "speaker" : "編集部",
        "top" : 0,
        "end" : 7.0,
        "comment" : "Repeat After YOU!",
        "text" : "このアプリは文字起こしを超高速に行うことに特化したアプリです。"
      },
      {
        "speaker" : "編集部",
        "top" : 7.0,
        "end" : 18.0,
        "comment" : "",
        "text" : "録音音声から聞き取った内容を、オウム返しで話して音声認識させることで、キーボードを使わずに超高速に文字起こしができます。"
      } 
   ],
    "audioFilePath" : "sample.mp3",
    "date" : 531674816.56146502
  }
]
```

##Json書き込み
JSONEncoderで構造体をエンコードして書き込み
・構造体は上で定義したもの

```Swift:Swift
    func saveEntriesToJson() {
        let jsonPath = liblaryPath + "/" + jsonFileName

        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        
        let data = try! jsonEncoder.encode(entries)
        let dataStr = String(data:data,encoding:.utf8)
        
        if dataStr?.isEmpty ?? true { return }
        do {
            // Stringデータをファイルに書き出し
            try dataStr!.write(toFile: jsonPath, atomically: true, encoding: .utf8)
        } catch _ {
            print("Write Error!  File : \(jsonPath)")
        }
    }

```

##他のアプリからファイルを受け取る
・受け取るファイルの形式をinfo.plistに設定
 <img width="561" alt="スクリーンショット 2018-02-18 16.53.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/45490/dcc71328-c0b6-bc63-774f-10d1f236c31b.png">

・受け取る処理をAppDelegeteに記述


```Swift:Swift
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        if let currentVc = window?.rootViewController?.presentedViewController {
            print(NSStringFromClass(currentVc.classForCoder))
            if NSStringFromClass(currentVc.classForCoder) == "RepeatAfterYou.ControlVC" {
                (currentVc as! ControlVC).busyMessage()
                return false
            }
            
        }
        // ファイルのURLを受け取る
        print("URL of recieved file = \(url)")
        // ファイルパスに変換
        let sourcePath:String = url.path
        let fileName = url.lastPathComponent
        let destPath = fileMoveToArchive(sourcePath)
        if destPath != nil {
           // ココで再生入力画面に遷移したい
            let storyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
            let ioVc:ControlVC = storyboard.instantiateViewController(withIdentifier: "ControlVC") as! ControlVC
            ioVc.entry = Entry(date:Date(),title:"",memo:"",audioFilePath:destPath!,audioFileName:fileName,texts:[TextPhrase(top:0,end:nil,speaker:"",text:"",comment:"")])
            ioVc.isNewEntry = true
            self.window?.rootViewController?.present(ioVc, animated: true, completion: nil)
        }
        
        return true

    }

```

・受け取ったファイルはInboxにあるので、管理するフォルダに移動して、Inboxのファイルは消す

```Swift:Swift
    /// 受信したファイルをローカルアーカイブに移動
    ///
    @objc func fileMoveToArchive(_ fromPath:String)->String? {
        let fm = FileManager()
        
        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
        let audioFolderPath = documentsPath + "/Audio"
        //Audioフォルダが無ければ作る
        var isDirExists : ObjCBool = false
        fm.fileExists(atPath: audioFolderPath, isDirectory:&isDirExists)
        if !isDirExists.boolValue {
            do {
                try fm.createDirectory(atPath: audioFolderPath, withIntermediateDirectories: true, attributes: nil)
            } catch let error1 {
                print("Error createDirectory : \(audioFolderPath) : " + error1.localizedDescription  )
                return nil
            }
        }
        let id = createFileID()
        let fileName = id + URL(fileURLWithPath: fromPath).lastPathComponent
        let destPath = audioFolderPath + "/" + fileName
        currentAudioFileName = destPath
        sourceAudioFileName = fromPath
        //異動先のファイルの有無をチェック
        if fm.fileExists(atPath: destPath) { // ファイル有りの場合は先に削除
            do {
                try fm.removeItem(atPath: destPath)
            } catch let error1 {
                print("Error move file : \(destPath) : " + error1.localizedDescription  )
                return nil
            }
        }
        //ファイルを移動
        do {
            try fm.moveItem(atPath: fromPath as String, toPath: destPath)
        } catch let error1 {
            print("Error move file : \(destPath) : " + error1.localizedDescription  )
            //return nil
        }
        
        // Inboxの余分なファイルは消す
        let inboxPath:String = documentsPath + "/Inbox/"
        var contents:[String] = []
        do {
            contents = try fm.contentsOfDirectory(atPath: inboxPath) as [String]
        } catch let error1 {
            print("Error serch file : \(inboxPath) : " + error1.localizedDescription  )
            return nil
        }
        
        for filePath in contents {
            let targetFilePath = inboxPath + filePath
            do {
                print(targetFilePath)
                try fm.removeItem(atPath: targetFilePath)
            } catch let error1 {
                print("Error remove file : \(filePath) : " + error1.localizedDescription  )
                return nil
            }
        }


        return fileName
    }

```

・おまけ：ローカルアーカイブからファイルを削除

```Swift:Swift
    @objc func removeAudioFile(_ filename:String) {
        let fm = FileManager()
        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
        let audioFolderPath = documentsPath + "/Audio/" + filename

        if fm.fileExists(atPath: audioFolderPath) {
            do {
                try fm.removeItem(at: URL(fileURLWithPath: audioFolderPath))
            } catch let error1 {
                print("Error file delete : \(audioFolderPath) : " + error1.localizedDescription  )
                return
            }
        }
    }
```

##UIColorをrgbで設定
・UIColorのextension

```Swift:Swift
extension UIColor {
    convenience init(rgb: UInt, alpha: CGFloat = 1.0) {
        let red: CGFloat = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
        let green: CGFloat = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
        let blue: CGFloat = CGFloat(rgb & 0x0000FF) / 255.0
        self.init(red: red, green: green, blue: blue, alpha: alpha)
    }
}

let NC_001_NADESHIKO = UIColor(rgb:0xDC9FB4) //撫子, NADESHIKO

```

##UITableview関連

###カスタムセルの部品に値を設定
・部品にTagを設定(Storyboard)
・Tagの部品を割り当て
・値を設定

```Swift:Swift
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath)
        let imageIcon = cell.viewWithTag(1) as! UIImageView
        let dateLabel = cell.viewWithTag(2) as! UILabel
        let titleLabel = cell.viewWithTag(3) as! UILabel
        
        imageIcon.image = #imageLiteral(resourceName: "icoMic")
        dateLabel.text  = ""
        titleLabel.text = "No Entry"
        
        cell.backgroundColor = UIColor.clear
        
        // 選択された背景色を白に設定
        let cellSelectedBgView = UIView()
        cellSelectedBgView.backgroundColor = ROW_COL_SELECTED
        cell.selectedBackgroundView = cellSelectedBgView

        return cell
    }

```

###スワイプして削除を有効に

```Swift:Swift

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    // スワイプ時に表示する文字列を設定
    func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
        return NSLocalizedString("Delete", tableName: "Message", comment: "削除")
    }

    func removeEntry(_ index:Int) {
        appDl.removeAudioFile(appDl.entries[index].audioFilePath)
        appDl.entries.remove(at: index)
        appDl.saveEntriesToJson()
        tvMainList.reloadData()
    }

    func removeConfirm(_ index:Int) {
        let alertController = UIAlertController(
            title: NSLocalizedString("Delete entry", tableName: "Message", comment: "エントリーの削除"),
            message: NSLocalizedString("delete-confirm-message", tableName: "Message",value: "Delete the data completely. Is it OK?", comment: "データを完全に削除します。よろしいですか？"),
            preferredStyle: .alert)
        
        let defaultAction:UIAlertAction = UIAlertAction(title: "OK",
                                                        style: UIAlertActionStyle.destructive,
                                                        handler:{
                                                            (action:UIAlertAction!) -> Void in
                                                            self.removeEntry(index)
        })
        alertController.addAction(defaultAction)
        
        let cancelAction = UIAlertAction(
            title: NSLocalizedString("Cancel", tableName: "Message", comment: "キャンセル"),
            style: .cancel, handler:nil)
        
        alertController.addAction(cancelAction)
        alertController.popoverPresentationController?.sourceView = view
        alertController.popoverPresentationController?.sourceRect = view.frame
        
        present(alertController, animated: true, completion: nil)
    }


 ```

###スワイプ時に削除以外にも動作を設定
・メール送信　（MFMailComposeViewControllerDelegate追加） 
・他のアプリにデータを渡す（UIDocumentInteractionControllerDelegate追加）

```Swift:Swift

    var documentInteraction:UIDocumentInteractionController? = nil

   
    // スワイプ時に削除以外にも動作を設定
    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let mailTitle:String = NSLocalizedString("Mail", tableName: "Message", comment: "メール")
        let mail: UITableViewRowAction = UITableViewRowAction(style: .normal, title: mailTitle) { (action, index) -> Void in
            self.exportByMail(indexPath.row)
        }
        mail.backgroundColor = NC_173_KAMENOZOKI
        let exportTitle:String = NSLocalizedString("Export", tableName: "Message", comment: "エキスポート")
        let export: UITableViewRowAction = UITableViewRowAction(style: .normal, title: exportTitle) { (action, index) -> Void in
            self.exportEntry(indexPath.row)
        }
        export.backgroundColor = NC_186_SORA
        
        let deleteTitle:String = NSLocalizedString("Delete", tableName: "Message", comment: "削除")
        let delete: UITableViewRowAction = UITableViewRowAction(style: .normal, title: deleteTitle) { (action, index) -> Void in
            self.removeConfirm(indexPath.row)
        }
        delete.backgroundColor = NC_014_KARAKURENAI
        
        return [export, mail, delete]
    }

    func exportByMail(_ index:Int) {
        if !MFMailComposeViewController.canSendMail() { // メール送信不可
            print("Mail can't be send")
            return
        }
        let csvString:String = appDl.entries[index].texts2CSV()
        let mailVC = MFMailComposeViewController()
        let toRecipients:[String] = [""] //Toのアドレス指定
        
        mailVC.mailComposeDelegate = self
        mailVC.setSubject("Repeat after YOU!: Data export(テキストデータエクスポート)")
        mailVC.setToRecipients(toRecipients)
        let mailMessage = "テキストデータをCSV形式で添付します。\nExport the data with csv format.\n　\n -- Repeat after YOU! --"
        mailVC.setMessageBody(mailMessage, isHTML: false)
        
        mailVC.addAttachmentData(csvString.data(using: String.Encoding.shiftJIS, allowLossyConversion: false)!, mimeType: "text/csv", fileName: "Export_output.csv" )
        self.present(mailVC, animated: true, completion:nil)

    }

    func exportEntry(_ index:Int) {
        let csvString:String = appDl.entries[index].texts2CSV()
        //let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
        let tempPath = NSTemporaryDirectory() as String
        let filePath = tempPath + "output.csv"
        print(filePath)
        print(csvString)
        let tmpUrl = URL(fileURLWithPath: filePath)
        
        do {
            try csvString.write(to: tmpUrl, atomically: true, encoding: .shiftJIS)
        } catch {
            print("File write error:",filePath)
            return
        }
        
        documentInteraction = UIDocumentInteractionController(url: tmpUrl)
        documentInteraction!.delegate = self
        documentInteraction!.uti = "public.text"
        if !documentInteraction!.presentOpenInMenu(from: self.view.frame, in: self.view, animated: true) {
            print("File send error")
            // 送信できるアプリが見つからなかった時の処理
            let alert = UIAlertController(
                title: NSLocalizedString("Alert!", tableName: "Message",comment: "注意！"),
                message: NSLocalizedString("send-fail-message", tableName: "Message",value: "Failed to send.", comment: "ファイルを送れるアプリが見つかりません"),
                preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
        
    }
    @objc func documentInteractionController(_ controller: UIDocumentInteractionController, willBeginSendingToApplication application: String?) {
        print("Start file sending to ",application ?? "")
    }
    @objc func documentInteractionController(_ controller: UIDocumentInteractionController,
                                       didEndSendingToApplication application: String?) {
        print("Complete file sending to ",application ?? "")
        
    }


```

##unwindSegue
・戻り先のControllerにunwindXXXXXを定義する
・戻り先ごとにXXXXXの名称を変える
・storyboardで戻りたいunwindXXXXXを選択してsegueを繋ぐ

```Swift:Swift

    @IBAction func unwindToMain(_ segue: UIStoryboardSegue) {
 　　　 // 戻った時にデータ更新などを実施
       print("Return to main")
       self.tableview.reloadData()
    }
```