20190709のSwiftに関する記事は4件です。

「iOS アプリ開発デザインパターン入門」を読んでの備忘録

こちらの本を読んで分からなかった内容を調べた(これから調べる)際のメモ

1. @objc func ~~~ の @objc って何ぞや?

答え
@objc を付けるとObjective-C側から呼び出す事ができる

詳細
1. Objective-Cではメソッドをコンパイルすると2つの隠し引数(selfcmd)を持った関数が生成される
2. Swiftでは1つのを隠し引数(オブジェクト自身)を持った関数が生成される
3. 隠し引数の数の違いによって相互利用が出来なくなってしまう
4. @objc を付けることでコンパイル時に2つの隠し引数を持ったメソッドが生成できる

補足
プロパティにも @objc をつけることができるみたい

参考
[Swift] @objcの話 [Objective-C]

2. @escaping って何ぞや?

答え
@escaping を付けるとクロージャをスコープ外で保持できる

class A {
    private let storedClosure: () -> ()
    // escapingを付けないとエラーになる
    init(closure: @escaping () -> ()) { 
        storedClosure = closure
    }
}

補足
1. @escaping したクロージャ内でフィールド変数を参照する場合には self を付ける
2. 循環参照に気を付ける

class A {
    private let storedClosure: () -> ()
    // escapingを付けないとエラーになる
    init(closure: @escaping () -> ()) { 
        storedClosure = closure
    }
}

class B {
    private var a: A?
    private var count = 0

    func doSomething() {
        // (2) 循環参照を避けるためにselfを弱参照する
        a = A(closure: { [weak self] in 
            // (1) フィールド変数のcountにアクセスするためにselfを付ける
            self?.count += 1
        })
    }
}

do {
    let b = B()
    b.doSomething()
}

参考
Swiftの @escaping と weak/unowned の理解
Swift 3 の @escaping とは何か

3. fileprivate って何ぞや?

答え
同一ファイル内であればアクセスできる修飾子
(名前の通りそのままですね)

private修飾子との違い

Hello.swift
class A {
    private var hoge = "hoge"
    fileprivate var fuga = "fuga"
}

let a = A()
print(a.hoge) // 'hoge' is inaccessible due to 'private' protection level
print(a.fuga) // fuga

最後に

初めての投稿で「@」が謎のリンクになっていたので、そちらの解決方法もメモしておきます

答え
@を<span>タグで囲う
@arusu0629@arusu0629
<span>@</span>arusu0629 → @arusu0629

参考
Qiitaのマークダウンで @(アットマーク)リンクの無効化に苦戦

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

Optionalを含んだクロージャ引数があるSwiftメソッドをObjective-Cから使おうとしたらエラーが出たので回避した

nilかそうでないかで処理を分けるために、クロージャの引数をOptionalにしたSwiftのメソッドがあったのですが、
Objective-Cから使用するために@objc属性を付加したら、エラーが出て使えませんでした。

エラー内容

Method cannot be marked @objc because the type of the parameter 4 cannot be represented in Objective-C

?‍♂️エラーが出たメソッド定義

ViewController.swift
@objc public func go(completion: (SomeType?) -> ())

?‍♂️エラー回避のための代替策

引数と戻り値を囲み、クロージャ自体をOptionalにする

ViewController.swift
@objc public func go(completion: ((SomeType) -> ())?)

引数をOptionalにすることを諦める

ViewController.swift
@objc public func go(completion: (SomeType) -> ())

この時の選んだ回避策

実際に行った回避策としては、クロージャを2つに分けました。

ViewController.swift
@objc public func go(success: (SomeType) -> (), failure: (SomeType) -> ())

あまり、遭遇機会もないかと思いますが、備忘録として書き残しておきます。

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

[Swift 5]TabBarのボタン(TabBarItem)をタップで画面をModal表示する簡単な方法

TabBarControllerで実装したボトムタブを選択すると、通常は画面が切り替わります。
今回は、特殊なボタンとして画面をModal(下から上へ出現)表示させたい時の実装です。

(真ん中のボタンが丸く縁取られているアプリに多いと思います)

Modalで開きたい画面クラスがTargetViewControllerと仮定して解説していきます。

ソースコード

TabBarController.swift
import UIKit

final class TabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        delegate = self // UITabBarControllerDelegate
    }
}

// MARK: - UITabBarControllerDelegateに適合
extension TabBarController: UITabBarControllerDelegate {
    // TabBarItemが選択された時に呼ばれる
    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
        // TabBarItemタップでModal表示をする画面を指定して実装
        if viewController is TargetViewController {
            if let newVC = UIStoryboard(name: "Target", bundle: nil).instantiateInitialViewController() {
                tabBarController.present(newVC, animated: true, completion: nil)
                return false
            }
        }
        return true
    }
}

実装手順

  1. delegate = selfを書くのを忘れない
  2. extentionUITabBarControllerDelegateに適合する
  3. tabBarController(_:shouldSelect:)->Boolメソッドを実装
  4. 開かれるViewControllerが対象のものだったら、という条件でif分岐
  5. あとは、ViewControllerをインスタンス化し、present(_:animated:completion)メソッドで画面をModal表示させればOKです!

NavigationControllerを使用している場合

TargetViewControllerでNavigationItemを使いたい等の理由でNavigationControllerを使用する場合

TabBarController.swift
if viewController.children.first is TargetViewController {

というようにすればNavigationControllerが間にあっても判定可能です。

もし、もっと簡単な方法があればぜひ教えてください。

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

[Swift][iOS]TableViewのセル再利用の罠

はじめに

iOSアプリでGUIを作る際にはTableviewを使うことが非常に多いと思います。
このTableview、デフォルトだとセルのクラスはUITableViewCellで、表示出来るUIが事前に定められています(テキスト、サブテキスト等)。
たとえば画像を表示したかったり、ボタンを表示したかったり、実装したいUIによってはデフォルトのクラスを継承したカスタムクラスを作ってやる必要があります。
そんな際にバグってしまったセルの取扱いについてまとめておきます。

バグった実装

例によってカスタムクラスをつくって、内部にはUITextFieldを設置して、ユーザーの入力を受け付けるようにすると、以下のようなバグが発生しました。
①1行目のセルのTextfieldに文字列を入力すると
IMG_2413.png
②下にスクロールすると7行目くらいのセルに文字列が入力されてしまっている
IMG_2414.png

バグの内容

実装したUIはセルの数が一定ではなく、ユーザーの入力によりセルが増減するものになります(初期状態はセルはひとつ)。セルを増やした際、1番目のセルに文字列を入力すると、それが「画面外の7番目くらいの別のセルにも反映されてしまう」バグに遭遇しました。

原因

ぐぐったら以下のQiitaの記事に辿りつきました。

UITableViewのあるセルに設定した値が、他のセルにも反映されてしまう
https://qiita.com/shikatani/items/88dff6ecf9684027862e

要は画面外にあるセルは内部的には描写されておらず、必要に応じて描写されるようになっているようです。
画面外から画面内に入ってくるセルは、画面内から画面外に出ていくセルを再利用しているのです。

対策

セルに登載されるデータはcellForRowAtで指定する訳ですが、事前にデータが固定されていないと実装が複雑になる感があります。今回の件は動的に登載されるべきデータが変更されることから、単純な実装ではうまくいかなかった訳です。上記のQiita記事では、バグは発生しているものの登載されるべきデータは静的に事前に指定されているので、セルを初期化すれば解決しています(∵画像の読み込み先URLは事前に決定されているから)。しかしながら本件のようなユーザーの入力にデータが依存するケースだと、入力されるデータを逐次保存しておかないと対応出来ないと思います。すなわち、予めセルに登載するデータ(=配列)を指定しておき、その配列の中身が更新される、という実装が必要になるかと。

終わりに

具体的な解決策は提示出来てませんが、Tableviewのセルに登載するデータは事前に指定してあげないと予期せぬ動作をするという事と、ググれば何でも出てくるという事を伝えたかった。

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