20210220のSwiftに関する記事は10件です。

【Swift】タプル型の便利な使い方(備考録)

導入

Swiftを使っていて、これまでタプル型を自分で実装して使う機会がなかったんですが、
開発していたらタプル型ってこういう時に使えばいいんだという気づきがあったため記事を書きました。

僕はMVPモデルでアプリ開発をしていた時に、タプル型の利便性に気付かされたので、今回はMVPモデルで作ったサンプルアプリを元に解説します。
※あまりに簡素なサンプルアプリであり、Modelは不要なので今回は、ViewとPresenterのみを使用します。

■ MVPモデルについてはこちらの記事が大変参考になりました
https://qiita.com/hicka04/items/25be38a90fdde29c97c2

サンプルアプリについて

下記に画面のキャプチャを載せました。
「計算ボタン」をタップすると、画面上に書かれている足し算を行い、水色の枠の部分に答えを表示します。
TupleApp.png
ありえないくらい簡素なサンプルアプリでございます。

ソースコード

ちなみに、今回やろうとしている処理はcalculate()がViewControllerから引数を受ける形にすればいいのですが、今回はタプル型を使いたいため、あえて引数を渡さないようにしています。
もっと複雑なアプリを作る場合、Presenter内の処理で、ViewControllerから引数として渡された値以外も、ViewControllerから取得して使いたいということがあると思います。
今回はそういったケースを想定しております。

MVPアーキテクチャを採用する場合、PresenterはViewが持っているLabelなどを直接参照したり、更新したりすることなく、PresenterProtocolで定義されたメソッドを通して、値の取得やViewへの更新依頼を行います。

今回は、getAdditionalNumber()を用いて、ViewControllerの値を取得しています。

■タプル型を活用した場合

1つのメソッドでViewControllerの2つの値を取得することが可能です。今回は、足し算に必要な2つの数を1つのメソッドで取得できています。
あとはPresenterで使いたい値を自由に取り出すだけで、簡単に処理できます。
今回は、calculateTuple()を呼び出した場合の処理になります。

■タプル型を使わない場合

逆に、タプル型を使わない場合、各ラベルを取得するのに別々のメソッドを書かないといけないので、PresenterProtocolに定義しないと行けないメソッドが増殖してしまいます。
そのたびにメソッドを増やさないといけないのは手間ですし、無駄なコードを書かないと行けないかなと思います。
今回は、calculate()を呼び出した場合の処理になります。

ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var labelNum1: UILabel!
    @IBOutlet weak var labelNum2: UILabel!
    @IBOutlet weak var labelAnswer: UILabel!

    var presenter: Presenter?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        presenter = Presenter(self)
    }

    @IBAction func touchCalculator(_ sender: Any) {
        // タプル型を使わない場合の処理を実行
        // presenter?.calculate()
        // タプル型を使う場合の処理を実行
        presenter?.calculateTuple()
    }
}


extension ViewController: PresenterProtocol {
    func updateAnswerLabel(answer: Int) {
        labelAnswer.text = "\(answer)"
    }

    // タプル型を使わない場合
    func getAdditionalNumber1() -> String {
        return labelNum1.text ?? ""
    }
    func getAdditionalNumber2() -> String {
        return labelNum2.text ?? ""
    }

    // タプル型を使う場合
    func getAdditionalNumber() -> (num1: String, num2: String) {
        return (labelNum1.text ?? "", labelNum2.text ?? "")
    }
}
Presenter.swift
import Foundation

protocol PresenterProtocol: AnyObject {
    func updateAnswerLabel(answer: Int) -> Void

    // タプル型を使わない場合
    func getAdditionalNumber1() -> String
    func getAdditionalNumber2() -> String
    // タプル型を使う場合
    func getAdditionalNumber() -> (num1: String, num2: String)
}

class Presenter {

    weak var view: PresenterProtocol?

    init(_ view: PresenterProtocol) {
        self.view = view
    }

    func calculate() {
        let _num1 = view?.getAdditionalNumber1()
        let _num2 = view?.getAdditionalNumber2()
        guard let num1 = _num1, let num2 = _num2 else {
            return
        }
        let sum = Int(num1)! + Int(num2)!
        view?.updateAnswerLabel(answer: sum)
    }

    func calculateTuple() {
        let tuple = view?.getAdditionalNumber()
        guard let num1 = tuple?.num1, let num2 = tuple?.num2 else {
            return
        }
        let sum = Int(num1)! + Int(num2)!
        view?.updateAnswerLabel(answer: sum)
    }
}

まとめ

今回は、タプル型の使い所について、自分の備考録を兼ねて書いてみました。
サンプルアプリが簡素だったため、メリットが捉えにくかったかもしれませんが、
「似たような属性の値を複数以上(配列にするほど長くもない程度)取得したい場合に、メソッドの戻り値として使えるよ」
ということが伝わっていればいいなと思います。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AVFoundation】Apple ProRAW/RAWイメージを撮影する

iOS 14.3以降を搭載したiPhone 12 Pro、iPhone 12 Pro MaxでApple ProRAWを撮影することができるようになりました。AVCaptureSession周りを構築して通常の写真撮影を行う方法に関する記事は既にたくさんありますので、ここではApple ProRAW/RAWを撮影するための設定と撮影/保存のハンドリング部分に絞って書きます。

AVCapturePhotoOutputの設定

Apple ProRAWを撮影するためにはAVCaptureSessionに追加したAVCapturePhotoOutputのisAppleProRAWEnabledを有効にする必要があります。Apple ProRAWではなく、通常のRAWイメージを撮影する時はこちらの項目の処理は不要です。
また、iPhone 12 Pro/iPhone 12 Pro Max以外の機種だと、isAppleProRAWSupportedはfalseになります。

   photoOutput.isAppleProRAWEnabled = photoOutput.isAppleProRAWSupported

AVCapturePhotoSettingsの設定

Apple ProRAW/RAWイメージを撮影してフォトライブラリに保存する場合は、「Apple ProRAW/RAWイメージ」 + 「加工済みイメージ」を一つのアセットとして保存する必要があります(以下の例だと「Apple ProRAW/RAWイメージ」 +「JPEGイメージ」)。そのため、AVCapturePhotoSettingsに「Apple ProRAW/RAWイメージ」と「加工済みイメージ」のフォーマットをそれぞれ指定する必要があります。

   let query = photoOutput.isAppleProRAWEnabled ?
       { AVCapturePhotoOutput.isAppleProRAWPixelFormat($0) } :
       { AVCapturePhotoOutput.isBayerRAWPixelFormat($0) }

   // Apple ProRAW/RAWイメージのフォーマットを指定
   guard let rawFormat =
           photoOutput.availableRawPhotoPixelFormatTypes.first(where: query) else {
       fatalError("No RAW format found.")
   }

   // 加工済みイメージのフォーマットを指定
   let processedFormat = [AVVideoCodecKey: AVVideoCodecType.jpeg]

   // 撮影するイメージのフォーマットをAVCapturePhotoSettingsに指定
   let photoSettings = AVCapturePhotoSettings(rawPixelFormatType: rawFormat,
                                              processedFormat: processedFormat)

   photoOutput.capturePhoto(with: photoSettings, delegate: self)

Apple ProRAW/RAWイメージの撮影

撮影を開始すると以下のデリゲートメソッドが2回コールされます。一度目のコールではApple ProRAW/RAWイメージが配信されるタイミング、2回目は加工済みイメージが配信されるタイミングでコールされます。ここではApple ProRAW/RAWイメージを一時的にtmpディレクトリに保存しておき、加工済みイメージを変数で保持しています。

   func photoOutput(_ output: AVCapturePhotoOutput,
                    didFinishProcessingPhoto photo: AVCapturePhoto,
                    error: Error?) {
       // 配信されるイメージデータの種類を判定する
       if photo.isRawPhoto {
           self.rawImageFileURL = self.makeUniqueTempFileURL(extension: "dng")
           do {
               // Apple ProRAW/RAWイメージをtmpディレクトリに保存
               try photo.fileDataRepresentation()!.write(to: rawImageFileURL!)
           } catch {
               fatalError("couldn't write DNG file to URL")
           }
       } else {
           // 加工済みイメージを変数で保持
           self.compressedData = photoData
       }
   }

   /**
    @brief Apple ProRAW/イメージファイルの保存先URLを生成する
    */
   private func makeUniqueDNGFileURL() -> URL {
       let tempDir = FileManager.default.temporaryDirectory
       let fileName = ProcessInfo.processInfo.globallyUniqueString
       return tempDir.appendingPathComponent(fileName).appendingPathExtension("dng")
   }

Apple ProRAW/RAWイメージの保存

撮影が完了したタイミングで以下のデリゲートメソッドがコールされます。「AVCapturePhotoSettingsの設定」で記載したように、Apple ProRAW/RAWイメージを撮影してフォトライブラリに保存する場合は「Apple ProRAW/RAWイメージ」 + 「加工済みイメージ」を一つのアセットとして保存する必要があります。加工済みイメージをメインイメージリソース、Apple ProRAW/RAWイメージを代替イメージリソースとしてアセットを保存するとApple ProRAW/RAWイメージの保存が完了します。

    func photoOutput(_ output: AVCapturePhotoOutput,
                     didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings,
                     error: Error?) {       
        PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in

            guard status == .authorized else { return }

            PHPhotoLibrary.shared().performChanges {
                // 加工済みイメージをメインイメージリソースとして保存
                let creationRequest = PHAssetCreationRequest.forAsset()
                creationRequest.addResource(with: .photo, data: compressedData, options: nil)

                // Apple ProRAW/RAWイメージを代替イメージリソースとして保存
                let options = PHAssetResourceCreationOptions()
                options.shouldMoveFile = true
                creationRequest.addResource(with: .alternatePhoto, fileURL: rawFileURL, options: options)

            } completionHandler: { success, error in
            }
        }
    }

写真アプリを起動して撮影した写真を選択した時、左上に「JPEG + RAW」と表示されていれば撮影成功です?
IMG_E7659A2629EB-1.jpg

以上になります。
それでは:cat:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】RxCocoa,RxSwiftで複雑なTableViewを実装する

RxCocoa,RxSwiftsectionをつけたり複雑な実装をするときはRxTableViewDataSourceTypeを準拠したカスタムUITableViewDataSourceを作成する必要があります。

RxTableViewDataSourceTypeを準拠したUITableViewDataSourceの作成

準拠させる際にNSObjectも準拠させる必要があるところに注意

class CustomTableViewDataSource: NSObject, UITableViewDataSource, RxTableViewDataSourceType {
    // セクションを作る場合は多次元配列にする
    // Modelは任意のデータ型
    typealias Element = [[Model]]
    var items: Element = [[]]

    func tableView(_ tableView: UITableView, observedEvent: Event<[[Model]]>) {
        Binder(self) { dataSource, element in
            dataSource.items = element
            tableView.reloadData()
        }
        .on(observedEvent)
    }
}

セクションの作成

class CustomTableViewDataSource: NSObject, UITableViewDataSource, RxTableViewDataSourceType {
    // セクションの個数
    func numberOfSections(in tableView: UITableView) -> Int {
        return items.count
    }

    // セクション内のcellの個数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items[section].count
    }
}

extension CustomTableView: UITableViewDelegate {
    // セクションに設定するViewを返す
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return CustomTableHeaderView()
    }

    // セクションの高さを返す
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 30
    }
}

セクション内のcellの描画

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.scheduleListCell.identifier, for: indexPath) as! CustomTableViewCell
        // ここでcellのinitととかを必要であればやる
        return cell
    }

スワイプでセルの削除などアクション有効にする

cellをスワイプしてアクションを表示させたい場合、canEditRowAtを有効にする必要がある。

func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

TableViewを描画する

前項までで作成したDataSourceを元にTableViewを描画する。

// Modelは任意のデータ
var items = BehaviorRelay<[[Model]]>(value: [])
let customDataSource = CustomTableViewDataSource()
private let disposeBag = DisposeBag()

private func observeList() {
    // itemsとcustomDataSourceを元にtableViewを描画する
    items
        .bind(to: tableView.rx.items(dataSource: customDataSource))
        .disposed(by: disposeBag)
}

// delegateを設定
private func setDelegate() {
    tableView.rx.setDelegate(self)
        .disposed(by: disposeBag)
}

// cellの選択
private func observeSelect() {
    tableView.rx.itemSelected.subscribe(onNext: { [weak self] indexPath in
        // 選択された時のアクションを記述
    }).disposed(by: disposeBag)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NSOutlineViewの基本的な実装

概要

  • NSOutlineViewを使って下記のように表示するだけの実装を行う。

image

GitHub

参考

実装

Model

  • 今回セルに対応されるModelは以下の通り。
  • NodeTypeはそのセルがGroupなのかを判断するのに使用している
enum NodeType {
    case group
    case parent
    case item
}

class Node {

    // MARK: - Properties

    var title: String
    var nodeType: NodeType
    var children: [Node]

    // MARK: - Lifecycle

    init(title: String,
         children: [Node] = [],
         nodeType: NodeType = .item) {
        self.title = title
        self.children = children
        self.nodeType = nodeType
    }

    // MARK: - Helpers

    var numberOfChildren: Int {
        children.count
    }

    var hasChildren: Bool {
        !children.isEmpty
    }
}

ViewController.swift

NSOutlineViewの設定

private func initializeOutlineView() {
    // 別のXibファイルから読み込む場合は登録が必要
    outlineView.register(MyCellView.nib(inBundle: nil),
                         forIdentifier: NSUserInterfaceItemIdentifier(rawValue: String(describing: MyCellView.self)))
    outlineView.delegate = self
    outlineView.dataSource = self
    outlineView.autosaveExpandedItems = true

    // スタイルの設定
    outlineView.selectionHighlightStyle = .regular
    outlineView.floatsGroupRows = false

    nodes.append(contentsOf: Node.createSampleNodes())
    outlineView.reloadData()
}

Starting in OS X version 10.5, passing 'nil' will expand each item under the root in the outline view.

outlineView.expandItem(nil, expandChildren: true)

NSOutlineViewDataSource

  • NSOutlineViewのデータに関するDelegateメソッドを実装する
  • itemが含むchildrenの数を返す
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
    if let node = item as? Node {
        return node.numberOfChildren
    }

    return nodes.count
}
  • 親に対する子の情報を与える
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
    if let feed = item as? Node {
        return feed.children[index]
    }

    return nodes[index]
}
  • itemが開閉可能か(trueを返すとCellViewにDisclosure Buttonが表示される)
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
    if let node = item as? Node {
        return node.hasChildren
    }

    return false
}
  • itemがグループ行のものかどうか
// グループ行かどうか(NSOutlineViewの設定如何では見た目と挙動が特別となる)
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
    guard let node = item as? Node else {
        return false
    }

    return node.nodeType == .group
}

NSOutlineViewDelegate

  • NSOutlineViewのDelegateメソッドを実装する
  • 各行に表示するCellViewの設定
public func outlineView(_ outlineView: NSOutlineView,
                        viewFor tableColumn: NSTableColumn?,
                        item: Any) -> NSView? {

    guard
        let myCellView = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: String(describing: MyCellView.self)), owner: self) as? MyCellView,
        let node = item as? Node
    else {
        return nil
    }

    myCellView.configureUI(withNode: node)

    return myCellView
}
  • 行選択が変更された場合に呼ばれるNotification
func outlineViewSelectionDidChange(_ notification: Notification) {
    guard let outlineView = notification.object as? NSOutlineView else {
        return
    }

    let selectedIndex = outlineView.selectedRow
    guard let node = outlineView.item(atRow: selectedIndex) as? Node else {
        return
    }

    print("Selected Title: \(node.title)")
}

NSTreeControllerの話

  • NSOutlineViewで調べているとNSTreeControllerを使っている例が多くヒットする。
  • ただCocoaBindingが絡んでいて、私の理解が浅いためちょっと使いこなせなかった。
    • ので今回はNSOutlineView単体で実装している
  • イメージに過ぎないが、NSTreeControllerを使うことで煩雑な処理、例えば複数選択のアクション・入れ替え・削除が簡単に扱えると思っている。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pod install実行時に`target overrides the OTHER_SWIFT_FLAGS build setting defined`が表示され、追加したライブラリがロードできない

環境

Xcode Version 12.4
MacOS BigSur 11.2.1

発生した問題

プロジェクトのPodfileにSnapKitを追記し、pod installを実行したところ、
Cannot load underlying module for ‘SnapKit'という問題が発生しました。
追加したライブラリのインポートはできるがロードができていない様子。

$ pod install

Analyzing dependencies
Downloading dependencies
Installing SnapKit (5.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 12 dependencies from the Podfile and 22 total pods installed.

[!] The `MySampleApp [Debug]` target overrides the `OTHER_SWIFT_FLAGS` build setting defined in `Pods/Target Support Files/Pods-MySampleApp/Pods-MySampleApp.debug.xcconfig'. This can lead to problems with the CocoaPods installation
    - Use the `$(inherited)` flag, or
    - Remove the build settings from the target.

error.png

解決方法

[!]の案内通り、対象の設定に$(inherited)を追加する。

手順

xcodeproj → TARGETS → Build Settings → 右上の検索窓でother_swiftとかで検索すると下のような項目がでる。
※ 特に設定してない場合は画像と違って空欄になっているはず

koumoku.png

対象スキームの右側の欄をダブルクリックすると下のような画面が開くので、
+アイコンをクリックして$(inherited)を項目に追加
inhe.png

これで設定は完了。
再度pod installするとメッセージが消えて、ライブラリがロードできるようになっていました。

説明

設定ファイルの内容を継承(inherited)して上書きしていく必要があるそうですが、
今回の場合はその継承関係が途中で切れていたため、正常に反映できていなかったようです。
参考: XCodeのBuild Settingsで、値が決まっていくルール -Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プッシュ通知の許可画面を出す。

『"このアプリ"は通知を送信します。よろしいですか?許可、許可しない』のやつを表示させる。

import UIKit
import Firebase
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        FirebaseApp.configure()
        //許可画面を出す。
        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self

            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プッシュ通知の許可画面を表示する

『"このアプリ"は通知を送信します。よろしいですか?許可、許可しない』のやつを表示させる。

import UIKit
import Firebase
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        FirebaseApp.configure()
        //許可画面を出す。
        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self

            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画面遷移時の値渡しのやり方【Swift 5】

はじめに

この記事では、異なるファイル間で(classを跨いで)値をやりとりする方法を紹介していきます。
また、付随するものとして、segueを使わずに画面遷移を行う方法も紹介していきます。

環境

・macOS catalina
・Xcode version 12.2
・swift 5.3.1

コードを用いた画面遷移

まずは遷移先の情報を取得します。
この際、""内にはStoryBoard IDを、as!以下は、遷移先のcocoa touch ファイルの名前を入力します。
スクリーンショット 2021-02-20 0.27.26.png
なお、StoryBoard IDは写真の赤丸で囲われた部分から入力します。この時、Use StoryBoard IDに忘れずにチェックを入れるようにしてください。

//遷移先の情報の取得
let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController



次に、遷移の方法を指定します。(任意)
上の行では、遷移先の画面をどう表示するかを指定することができます。ここでは、.coverVertical, crossDissolveなどの方法を指定することができます。
下の行では、どのようなアニメーションで遷移を行うのかを指定することができます。

nextView.modalTransitionStyle = .crossDissolve   //遷移の仕方の設定
nextView.modalPresentationStyle = .fullScreen    //遷移先の表示方法の設定



最後に、このコードを実行すると遷移が完了します。

self.present(nextView, animated: true, completion: nil)    //遷移の実行


コードでの遷移を行うメリットとしては、segueを使うよりも、画面遷移の際のイベントを実装しやすいことが挙げられます。
したがって、遷移の際に何らかの処理を行う際には、上記のようにコードを用いた方法で行う方が良いです。

最後に、もう一度全体のコードを載せておきます。

let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
nextView.modalTransitionStyle = .crossDissolve
nextView.modalPresentationStyle = .fullScreen
self.present(nextView, animated: true, completion: nil)

遷移先に値を渡す

ここでは、SecondViewController.swiftに、FirstViewController.swiftから値を渡す場合を考えます。

まずは、値の受け手である、SecondViewController.swiftに、以下のように記述します。

//SecondViewController.swift
import UIKit

class SecondViewController: UIViewController{
      override func viewDidLoad() {
          super.viewDidLoad()
      }
      //FirstViewControllerから受け取る値をいれる変数の宣言
      var reciever = ""
}

この際、reciverが、値を受け取る変数です。実際に使うときは、ここに、値を受け取る変数を自由にセットしてください。

続いては、FirstViewController.swiftの中に、SecondViewController.swiftに値を渡す処理を入力していきます。

//FirstViewController.swift
import UIKit

class FirstViewController: UIViewController{
      override func viewDidLoad() {
          super.viewDidLoad()
      }

      //→ここから遷移のコードなので、実際は遷移したいタイミングで実行される場所に書きます (ex. IBActionの処理の中など)
      //遷移先を取得(上記参照)
      let nextView = storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! ViewController
      //""内には、SecondViewControllerのstoryBoard IDを入力します
      nextView.modalTransitionStyle = .crossDissolve
      nextView.modalPresentationStyle = .fullScreen

      //SecondViewController.swift内の変数recieverに文字列"Hello, world!"を代入
      nextView.reciever = "Hello, world!"

      //遷移を実行
      self.present(nextView, animated: true, completion: nil)
}

上記の手順で、異なるファイル間での値渡しは完了です。

注意事項

遷移先(SecondViewController)にIBActionやIBOutlet接続されている値(LabelやTextfieldなど)は遷移時に指定してしまうとエラーが出てしまいます。
そのような時は一旦遷移先(SecondViewController)に一時的に値を受け取る変数を適当に宣言して、そちらに値を渡して、遷移後にSecondViewControllerのViewDidLoadなどでIBActionやIBOutlet接続されている値に代入するようにしてください。

自作アプリの宣伝

私たちは、自作のリマインダーアプリ「タスクリマインダー -TaskReminder 課題管理-」を公開しています。
ダウンロードはこちらから!
https://apple.co/3jJZ1Cyhttps://apple.co/3jJZ1Cy

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画面遷移時の値渡しのやり方

はじめに

この記事では、異なるファイル間で(classを跨いで)値をやりとりする方法を紹介していきます。
また、付随するものとして、segueを使わずに画面遷移を行う方法も紹介していきます。

環境

・macOS catalina
・Xcode version 12.2
・swift 5.3.1

コードを用いた画面遷移

まずは遷移先の情報を取得します。
この際、""内にはStoryBoard IDを、as!以下は、遷移先のcocoa touch ファイルの名前を入力します。
スクリーンショット 2021-02-20 0.27.26.png
なお、StoryBoard IDは写真の赤丸で囲われた部分から入力します。この時、Use StoryBoard IDに忘れずにチェックを入れるようにしてください。

//遷移先の情報の取得
let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController



次に、遷移の方法を指定します。(任意)
上の行では、遷移先の画面をどう表示するかを指定することができます。ここでは、.coverVertical, crossDissolveなどの方法を指定することができます。
下の行では、どのようなアニメーションで遷移を行うのかを指定することができます。

nextView.modalTransitionStyle = .crossDissolve   //遷移の仕方の設定
nextView.modalPresentationStyle = .fullScreen    //遷移先の表示方法の設定



最後に、このコードを実行すると遷移が完了します。

self.present(nextView, animated: true, completion: nil)    //遷移の実行


コードでの遷移を行うメリットとしては、segueを使うよりも、画面遷移の際のイベントを実装しやすいことが挙げられます。
したがって、遷移の際に何らかの処理を行う際には、上記のようにコードを用いた方法で行う方が良いです。

最後に、もう一度全体のコードを載せておきます。

let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
nextView.modalTransitionStyle = .crossDissolve
nextView.modalPresentationStyle = .fullScreen
self.present(nextView, animated: true, completion: nil)

遷移先に値を渡す

ここでは、SecondViewController.swiftに、FirstViewController.swiftから値を渡す場合を考えます。

まずは、値の受け手である、SecondViewController.swiftに、以下のように記述します。

//SecondViewController.swift
import UIKit

class SecondViewController: UIViewController{
      override func viewDidLoad() {
          super.viewDidLoad()
      }
      //FirstViewControllerから受け取る値をいれる変数の宣言
      var reciever = ""
}

この際、reciverが、値を受け取る変数です。実際に使うときは、ここに、値を受け取る変数を自由にセットしてください。

続いては、FirstViewController.swiftの中に、SecondViewController.swiftに値を渡す処理を入力していきます。

//FirstViewController.swift
import UIKit

class FirstViewController: UIViewController{
      override func viewDidLoad() {
          super.viewDidLoad()
      }
      //遷移先を取得(上記参照)
      let nextView = storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! ViewController
      //""内には、SecondViewControllerのstoryBoard IDを入力します
      nextView.modalTransitionStyle = .crossDissolve
      nextView.modalPresentationStyle = .fullScreen

      //SecondViewController.swift内の変数recieverに文字列"Hello, world!"を代入
      nextView.reciever = "Hello, world!"

      //遷移を実行
      self.present(nextView, animated: true, completion: nil)
}

上記の手順で、異なるファイル間での値渡しは完了です。

自作アプリの宣伝

私たちは、自作アプリ「タスクリマインダー -TaskReminder 課題管理-」を公開しています。
ダウンロードはこちらから!
https://apple.co/3jJZ1Cyhttps://apple.co/3jJZ1Cy

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】API、JSON解析を使う

今回はこちら↓で作ったSwift5Boketeアプリの復習
【iOS14対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp

61dfbaebcff9d63d53eabc251ec044c4.gif

検索欄にキーワードを入力すると、関連する画像が出てきます。
気に入った画像に適当にネタを入れて決定ボタンを押し、シェアするボタンを押すとスクリーンショットが作られ、Twitter等に投稿することができます。

このアプリでは
①ライブラリの導入方法
②APIの使い方
③JSONの使い方
を学びました。

cocoapodsでライブラリを導入する

ライブラリに関しては動画内でもこちらのサイトを参照されていたので、このサイトを参考にするのが良いかと思います。

今回使用するライブラリは

pod 'SwiftyJSON' => JSONを使用するライブラリ
pod 'Alamofire' => ネットワークを使用するライブラリ
pod 'SDwebImage' => URLで引っ張ってきた画像を高速で使用することができるライブラリ

の3つです。
UIは以下の通り。
スクリーンショット 2021-02-19 12.11.18.jpg
画像上に各パーツの名前(インスタンス名)を書いています。

ViewController↑で導入したライブラリを使用するのと、デバイス内のアルバムを使用するために

ViewController.swift
import Alamofire
import SwiftyJSON
import SDWebImage
import Photos

classの前に追記します。

作成したスクリーンショットをアルバムに保存するためのプログラムを、viewDidLoad内に記述します。

viewController.swift
PHPhotoLibrary.requestAuthorization { (status) in
  switch(status){
    case .authorized: break
    case .denied: break
    case .noDetermined: break
    case .restricted: break
    }
}

APIについて

APIはApplication Program Interfaceの略です。
簡単に言うと、とあるサービスがソフトウェアの一部を公開して、他のソフトウェアに機能を使えるようにできます(多分)
公開元が発行しているAPIキーを使い、発行元が公開している仕様書に従ってプログラムを記述することで、自分のソフトウェアに機能を導入することができます。

今回使用するのがpixabeyと言う無償画像提供サイトのAPI。
このAPIを使用して行いたいのは、アプリ内のSearchTextFieldに入力したキーワードで画像をpixabayから引っ張り出してくることです。
流れとしては、https://pixabay.com/api/?key=[個人のAPIキー]&q=[検索ワード]pixabayのサーバーにリクエストを送ると、json形式でレスポンス(検索結果)が返ってくるので、このデータにjson解析を行って画像を引っ張ってきます。

例えば検索ワードをyellow+flowerとした場合、pixabayのレスポンスは以下のような形で返ってきます。
スクリーンショット 2021-02-19 14.05.10.png

プログラムを書きます。
まず、画像のURLを取得するメソッドを作成します。

ViewController.swift
func getImages(keyword:String) {
  //APIを使う
    let url = "https://pixabay.com/api/?key=[個人のAPIキー]&q=\(keyword)"
    //Alamofireを使ってHTTPリクエストを行う
    AF.request(url, method: get, parameters: nil, encoding: JsonEncoding.default).responseJSON{ (response) in //<= クロージャー
      switch response.result{
        case .success: //<=サーバーからリクエストを正常に受け取れたかどうかで条件分岐
    //JSON形式で返ってきたデータにJSON解析を行う
          let json:JSON = JSON(response.date as Any) //<=ここでデータを取得する
    //必要なデータを取り出し変数に格納する
          var imageString = json["hits"][self.count]["webformatURL"].string //<=hitsの配列内にあるwebformatURLをとってくる
          self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed:nil)

        case .failure:
          print(error)
        }
      }
    }

流れをざっくり説明すると...
検索欄に入力されたキーワードが、APIキーと共にパラメーターになってpixabayに送信されます。
すると、上のようなデータがJSON形式と言う形で返ってきます。
そのデータは辞書型で値が入っています。
その中の、hitsというキーが持っている配列の中に、検索ワードと一致する分だけ同じ数の配列が入っています。
各配列の中のwebformatURLを、ViewControllerodaiImageViewに表示させたい、というわけです。

そして次のお題ボタンをタップすると、次の画像が表示されるようにしたいので、nextOdaiという名前でアクションを作成します。

ViewController.swift
@IBAction func nextOdai(_ sender: Any) {
  count += 1 //<= 冒頭でvar count = 0と宣言しておく
  if searchTextField.text == ""{
    getImage(keyword: "funny")
  } else {
    getImage(keyword: searchTextField.text!)
  }
}

countをインクリメントすることで、次のお題ボタンを押すたびにJSON内の配列を順番に参照することができます。

但し、次へボタンを押しまくって、["hits"]内の配列の数よりcountが多くなってしまうと、参照する値がなくなってしまい、エラーになってしまします。これを避けるために、getImagesメソッド内のlet imageString =....stringの行の次に、以下のプログラムを追記します。

ViewController.swift
if imageString == nil {
  imageString = json["hits"][0]["webformatURL"].string
                    //↓これは画像を表示できるようにするために導入したSDWebImageのメソッドです
  self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed: nil)
} else {
  self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed: nil)
}

次に、?ボタンを押した時の機能を書きます。

ViewController.swift
@IBAction func searchAction(_ sender: Any) {
  self.count = 0
  if searchTextField.text == "" {
    getImages(keyword: "funny")
  } else {
    getImages(keyword: searchTextField.text!)
  }
}

上との違いはというとself.count = 0ですが、searchActionは検索して一番最初のデータを持ってくるので、count = 0、すなわち["hits"][0]を持ってくる必要があるのでこの記述が必要です。(多分)

次に決定ボタンが押された時の機能を書きます。
決定ボタンが押された時の流れとしては、ViewControllerで選択した画像とコメントを次の画面に遷移して渡す必要があります。
あと、画面間で値を受け渡しするためのprepare for segueも一緒に書きます。

ViewController.swift
@IBAction func done(_sender: Any) {
  performSegue(withIdentifier: "next", sender: nil)
}
override func prepare(for segue: UIStoryBoardSegue, sender: Any?) {
  let shareVC = segue.destination as? ShareViewController
  shareVC?.resultImage = odaiImageView.image!
  shareVC?.commentString = commentTextView.text
}

遷移先のプログラムを書きます。

ShareViewController.swift
var resultImage = UIImage()
var commentsString = String()
var screenShotImage = UIImage()

上の2つの値はViewControllerですでに値を受け取っているので、viewDidLoadメソッド内で

ShareViewController.swift
resultImageView.image = resultImage
commentLabel.text = commentString

と記述して遷移先の画面に値を反映します。

次にスクリーンショットの機能を書きます。
これは何も考えず、こういう風に書くんだな、と思っておくのが良さそうです。

ShareViewController.swift
func takeScreenshot(){
 let width = CGFloat(UIScreen.main.bounds.size.width)
 let height = CGFloat(UIScreen.main.bounds.size.height/1.3)
 let size = CGSize(width: width, height: height)

 UIGraphicsBeginImageContextWithOptions(size, false, 0.0)

 self.view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
 screenShotImage = UIGraphicsGetImageFromCurrentImageContext()!
 UIGraphicsEndImageContext()
}

そして、共有ボタンを押したらスクリーンショットが発動して共有できるようにしたいので、↑のメソッドを利用して、

ShareViewController.swift
@IBAction func share(_ sender: Any) {
  //スクリーンショットを撮る
  takeScreenshot()

  let items = [screenShotImage] as [Any]
 //アクティビティビューに乗っけてシェアする
  let activityView = UIActivityViewController(activityItems: items, applicationActivities: nil)
  present(activityView, animated: true, completion: nil)
}

これで一通り完成です。

感想

Ruby on Railsでもいくつかライブラリ使用しましたが、Swiftのもあるんですね(あほ)。
!だったり?だったりself書いたりって細かい違いのところがまだ把握できてないので、今後の学習の中でつかんでいきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む