Swift の Class, Protocol を Objective-C で使う

Objective-C 側で ProjectName-Swift.h を import すれば使えるでしょとか思っていたのですが、すんなり使えなかったので書いておきます。

Class を使う

SampleObjcClass.h と SampleObjcClass.m からなる Objective-C のクラス内で、 Swift で書かれた SampleSwiftClass を使いたいとします。 この方法は、ヘッダファイル (.h) と インプリメンテーションファイル (.m) で違います。

インプリメンテーションファイルでは、ProjectName-Swift.h を import するだけで大丈夫です。

// SampleObjcClass.m

#import "ProjectName-Swift.h"

@implementation SampleClass

@end

ヘッダファイルの場合は、対象のクラスを class で宣言することで呼び出せるようになります。

// SampleObjcClass.h

@class SampleSwiftClass

@interface SampleObjectiveCClass

@end

継承する

Objective-C のクラスで Swift のクラスを継承することはできないようです。
そのため、 Swift のクラスを継承したいときは Objective-C のクラスを Swift に書き換える必要があります。何か別の方法あったら教えていただきたいです、、、

Protocol を使う

先ほど同様、 SampleObjcClass.h と SampleObjcClass.m からなる Objective-C のクラス内で、 Swift で書かれた SampleSwiftProtocol を使いたいとします。

この場合、 Swift 側で SampleSwiftProtocol の定義に @objc を付ける必要があります。

// SampleSwiftProtocol.swift

@objc protocol SampleSwiftProtocol {

}

Objective-C 側は、クラスの場合と同様にインプリメンテーションファイルでは、ProjectName-Swift.h を import するだけで大丈夫です。

// SampleObjcClass.m

#import "ProjectName-Swift.h"

@implementation SampleClass ()

@end

ヘッダファイルの場合は、対象のプロトコルを @protocol で宣言することで呼び出せるようになります。

// SampleObjcClass.h

@protocol SampleSwiftProtocol

@interface SampleObjectiveCClass

@end

適用する

適用する定義は、インプリメンテーションファイルにて記述する必要があるようです。ヘッダファイルではエラーとなってしまいます。

// SampleObjcClass.m

#import "ProjectName-Swift.h"

@implementation SampleClass () <SampleSwiftProtocol>

@end
// SampleObjcClass.h

@protocol SampleSwiftProtocol

// エラーになる
@interface SampleObjectiveCClass <SampleSwiftProtocol>

@end

参照

developer.apple.com

iOSDC Japan 2016 に参加してきた!

8/19, 20 にかけて開催された iOSDC Japan 2016 に参加してきました!
記念すべき第一回でした!
次回開催についてですが、 SNS やブログ等で拡散されていけばあるかもということでした。是非来年も開催されてほしいです!

iOSDC Japan 2016 の詳細はこちら↓

iosdc.jp

自分は iOS 開発を始めて期間が浅く、話を聞くのでいっぱいいっぱいで全然メモを取れませんでしたが、わずかに残したのでそれをちょろっと書きます。発表資料を公開されている方もいるので、そちらのリンクも貼っておきます。


AB Tests in Mobile App

kazunori kikuchi (@kichikuchi) さん

speakerdeck.com

iOS アプリ開発の補助ツールのベストプラクティス

宇佐見 公輔 (@usamik26) さん

speakerdeck.com

ライブラリ管理

Carthage とか

  • アプリとライブラリが分離される
  • バージョンが分かる

リソース取り込み

SwiftGen とか

コードチェック

SwiftLint とか

  • Warning を見逃さない
  • 量が多すぎて辛いならば、チェックをゆるくして、チェックを継続したほうが良い
  • そうすれば大事な Warning を見落とさない
  • SwiftLint の autoformat とかのコードフォーマットで自動変換できる

デザイナーにStoryboardをお任せする技術

Hiroki Kato (@cockscomb) さん

iosdc.jp

iOSアプリのリモートサポートツール「ミレタ」の作り方 #WebRTC #Swift #PrivatePod

Yuichiro Masui (@masuidrive) さん

iosdc.jp

Swift で JavaScript 始めませんか?

熊谷 友宏 (@es_kumagai) さん

www.slideshare.net

Xcode で快適なデバッグライフを追い求める

Toshihiro Morimoto @dealforest さん speakerdeck.com

クラッシュしたら AppDelegate だった

  • ExceptionBreakpoint を追加
    exception が発生したタイミングで breakpoint
    意図とは違うタイミングで break してしまうことが

  • Diagonostics を設定
    不正なメモリ操作を検知

どの ViewController かわからない

Symbolic Breakpoint with action を使って viewDidLoad と viewWillApear が呼ばれた時に break されるようにする

起動時に ViewController を指定したい

EnvironmentVariables で設定する

端末のログやファイルを取得したい

LLDB を使って slack に送信


全然メモ取っていなくてすみません。。。

constraint を動的に追加 / 削除する

AutoLayout でレイアウトに必要な constraint を追加する場合、 Storyboard 上で指定する方法と、コードで指定する方法があります。
Storyboard では静的な constraint であれば簡単に追加できますが、動的に追加、そして削除したい場合には、コードで指定しなければならないと思います。

以下では、対象の constraint はつくられているとして、その constraint を動的に追加 / 削除する方法を記述します ( iOS8 以上) 。

方法は、メソッドによる指定とプロパティによる指定の 2 通りがあります。

メソッドによる指定

これらのメソッドは、引数に [NSLayoutConstraint] をとるため、複数の constraint を 1 行で指定することができます。

let constraints = [hogeConstraint, fugaConstraint]

NSLayoutConstraint.activateConstraints(constraints) // constraint の追加

NSLayoutConstraint.deactivateConstraints(constraints) // constraint の削除

プロパティによる指定

単一の constraint を指定する場合にはこちらの方法で良いかと思います。
個人的にはこちらの方が読みやすくて好きです。

hogeConstraint.active = true // constraint の追加

hogeConstraint.active = false // constraint の削除

注意点

注意したいのは、 削除した constraint を再び追加する場合です。
具体的には、 Stroyboard 上で指定した constraint を IBOutlet で接続し、それを状況に応じて削除、そして再び追加するということをしたい場合です。

このとき、constraint の参照を weak として IBOutlet で接続してしまうと、削除したのちに再び追加しようとするとエラーになってしまいます。

@IBOutlet internal var hogeConstraint: NSLayoutConstraint! // weak 参照

hogeConstraint.active = false 

hogeConstraint.active = true // 実行時エラーになる

このエラーを解消するためには、 参照を strong にする必要があるようでした。

@IBOutlet internal var hogeConstraint: NSLayoutConstraint! // strong 参照

hogeConstraint.active = false 

hogeConstraint.active = true // エラーにならない

追記

strong にする必要があると書きましたが、強参照にすると循環参照の恐れがあるので、weak のままで true にする直前にフォースアンラップ等で nil チェックをする方が良さそうかなという気がしています。

参照

tomoyaonishi.hatenablog.jp

UITableView のセルの高さを可変にする

高さが可変の UILabel や UIImageView を UITableView のセルに配置したときには、もちろんセルの高さも可変にしたいと思います。

その方法を書きます。

ちなみに UILabel の行数を可変にする方法はこちらのエントリで書きました。

komaji504.hateblo.jp

セルの高さを可変にする

と言っても簡単で、以下のメソッドを実装してセルの見積もりの高さと、実際の高さを UITableViewAutomaticDimension で指定するだけです。

    func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return 100 // セルの高さの見積もり
    }
    
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }

UITableView は estimatedRowHeight, rowHeight プロパティを持っているので、上記のメソッドを実装せずに、以下のように viewDidLoad() 等で直接指定しても大丈夫です。

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.estimatedRowHeight = 100  // セルの高さの見積もり
        tableView.rowHeight = UITableViewAutomaticDimension
    }

見積もりの高さ

UITableView では UILabel 等の高さからセルの高さを計算させる必要があるのですが、計算処理が複雑な場合に負荷が大きくなってしまいます。
そこで、見積もりの高さを指定することで、 実際にセルを描画するタイミングまで見積もりの高さを使って計算させ、実際の計算を遅延させることができます。

そのため、この高さの指定は大体で構いません。

注意点

estimatedRowHeight の値を CGFloat.min で指定すると、 iPhone5 等の 32ビットCPU のデバイスではクラッシュしてしまいます。
また、 64ビットCPU のデバイスでも、描写されたセルの個数が指定した数でなかったりすることがあるようなので、 CGFloat.min で指定せずに、マジックナンバーで指定するのがよさそうです。

UILabel の行数を可変にする

Storyboard で UILabel を選択し、 Attributes inspector から Lines を 0 にするだけです。

f:id:komaji504:20160731171427p:plain

この値が UILabel の行数で、デフォルトの 1 のままだと 1 行固定となり、 テキストが複数行にわたる場合は、 ... と省略されて表示されるようになります。

今回のように 0 にすると、行数にかかわらず複数行で表示することが可能となります。


ちなみに、UILabel の height が固定になっていると、その height で表示できる行数だけしか表示されず、以降のテキストは ... と省略されてしまいます。

そのため、この設定とは別に高さが可変になるような Auto Layout も設定しなければならないと思うので注意が必要です。

Objective-C の Category と Swift の Extension

既存のクラスを拡張したいときに、 Objective-C であれば Category 、 Swift であれば Extension により実装していきます。 それぞれの実装方法の例です。

Objective-C

NSDate を拡張して sampleMethod というメソッドを実装したい場合、 Objective-C では以下のように記述します。

// NSDate+SampleProject.h

#import <Foundation/Foundation.h>

@interface NSDate (SampleProject)

- (void)sampleMethod;

@end
// NSDate+SampleProject.m

@implementation

- (void)sampleMethod
{
    // hoge
}

@end

Swift

上記と同じメソッドを Swift の Extension で記述すると以下のようになります。

// NSDate+SampleProject.swift

import Foundation

extension NSDate {

    func sampleMethod() {
        // hoge
    }

}

Objective-C から Swift の Extension を利用する

ちなみに、 Objective-C から Swift の Extension を利用したいときは、 Objective-C ファイルで #import "sampleProject-Swift.h" を記述しておくだけで大丈夫です。

Swift でページングを実装する

UICollectionView を使って一覧画面を作成していて、画面を一番下までスクロールしたら次ページをロードして表示する、というようにページング処理をさせたいときには、 UICollectionReusableView と UIActivityIndicatorView を使うと良いそうです。

これらを使ってページング処理をさせるときには、 UICollectionReusableView でフッターとなる View を作成し、 UIActivityIndicatorView でローディングインジケータを作成することになります。

ページング処理の大まかな流れは以下の通りです。

  1. 画面を一番下までスクロールするとフッターが表示される。
  2. フッターが表示されたらインジケータを表示する。ぐるぐるさせる。
  3. 次ページを読み込む処理を実行する。
  4. 次ページを読み込む処理が完了したらぐるぐるやめる。インジータを隠す。

フッターが表示されたら という部分は、 collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) を使うことで実装できます。

具体的には以下のように記述します。

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    if kind == UICollectionElementKindSectionFooter {
        let reusableView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "UICollectionReusableView に設定した Idenfier", forIndexPath: indexPath)
        // インジケータをぐるぐるさせる処理
        // 次ページを読み込む処理

        return reusableView
    }

    return UICollectionReusableView()
}