17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

qnoteAdvent Calendar 2016

Day 14

KotlinとSwiftが似てるってホント?

Last updated at Posted at 2016-12-14

qnote Advent Calendar 2016 14日目を担当いたします。
チャン・ジュン(日本人)です。
よろしくどうぞ

はじめに

Kotlinしかネタがないので今回もやっぱりKotlinのお話ですが、ついにSwiftが出てきます。
ちなみに私はiOS開発初心者なのでSwift側の見解には誤りが多分に含まれている可能性がありますのでご注意ください。
調べたらできるとか多分あります。

比較してみよう

さて、今回はきっと誰もが通ったタイマーアプリをKotlinとSwiftでそれぞれ書いてみました。
特に違いのある部分のみを抜粋してみていこうと思います。

グローバル変数

Kotlinの場合

//トップレベルに記述
var defaultTimerCount = 300
var timerCount = defaultTimerCount

class MainActivity : AppCompatActivity() {

	//グローバル変数として記述
    var isRunning = false
}

Kotlinではタイマーのカウントダウン表示に利用するtimerCountにタイマーのカウントの初期化で使うdefaultTimerCountを代入するという書き方ができます。

var timerCount = defaultTimerCount

※グローバル変数同士でも代入は可能です。

Swiftの場合

var defaultTimerCount: Int64 = 300
var timerCount: Int64 = defaultTimerCount

class ViewController: UIViewController {

    @IBOutlet weak var timerLabel: UIButton!
    @IBOutlet weak var controlBtn: UIButton!
    @IBOutlet weak var pauseBtn: UIButton!

    var isRunning = false
}

Swiftではトップレベルに記述することができませんでした。
とはいえiOS側にはトップレベルに記述する理由がないので今回は特に問題ありません。
また、グローバル変数をグローバル変数に代入できませんでした。

コードを修正しました。
@t-ae さんのご指摘の通り、改めて記述したところ問題なく上記コードで動きました。
ありがとうございます。

UI系のオブジェクトはiOS側にはありますが、Android側はkotlinExtensionsを利用して対応するので不要です。

遅延実行

Kotlinの場合

fun countDown() {
    if (!isRunning) {
        return
    }

    Handler().postDelayed({
        if (!isRunning) {
            return@postDelayed
        }

        timerCount--
        updateTimerLabel()
        if (timerCount > 0) {
            countDown()
            return@postDelayed
        }

        Handler().postDelayed({
            initTimer()
        }, 1000)
    }, 1000)
}

Androidの定期的な繰り返し処理は遅延実行をよく使います。
今回はtimerCountをデクリメントして0になるまでカウントダウンメソッドを呼び続けます。
timerCountが0になった後再度遅延実行でinitTimer()を呼んでいるのは0の表示後に表示が初期化されるようにするためです。

Swiftの場合

func countDown() {
    if (!isRunning) {
        return
    }

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
        if (!self.isRunning) {
            return
        }
        
        self.timerCount -= 1
        self.updateTimerLabel()
        if(self.timerCount > 0) {
            self.countDown()
            return
        }

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
            self.initTimer()
        }
    }
}

iOSでは遅延実行ではあまりやらないようなのですが、今回はAndroidの実装に合わせて遅延実行で実装しました。
遅延時間の設定はAndroidとiOSで異なるようです。
Androidでは単純に1秒を設定しているのに対して、iOSでは現在時間+1秒を設定します。
また、timerCountのデクリメントは--が使用不可(Swift3)なので-=を使用しています。
Swift3の--の使用不可は言語間の壁を大きくする修正のように感じますが、より安全なコーディングを考えると合理的な修正のようにも思えます。

モーダルなダイアログを表示する

Kotlinの場合

class TimerSettingDialogFragment() : BottomSheetDialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = BottomSheetDialog(context)
        dialog.setContentView(R.layout.timer_setting_dialog)
        val listItem = arrayOf("01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00")
        val listView = dialog.findViewById(R.id.item_list) as ListView
        listView.adapter = ArrayAdapter<String>(context, android.R.layout.simple_list_item_1,listItem)
        listView.setOnItemClickListener {
            adapterView, view, i, l ->
            defaultTimerCount = (i+1) * 60
            (context as MainActivity).initTimer()
            dismiss()
        }
        dialog.findViewById(R.id.cancel_btn)?.setOnClickListener {
            dismiss()
        }
        return dialog
    }
}

Androidでモーダルなダイアログを表示するにはBottomSheetDialogFragmentBottomSheetDialogを利用する必要があります。
ダイアログ表示の呼び出しは下記の通りです。

TimerSettingDialogFragment().show(supportFragmentManager, "tag")

タイトルやメッセージはAlertDialogとは異なり、直接コード上でsetすることができなかったので、レイアウトファイルに記述しています。
リストに表示するアイテムもxmlに記述すればコードはスッキリしますが今回はベタに書いてあります。
また、ダイアログのViewの取得ではFragmentにセットされていないのでkotlinExtensionsが利用できず、気になるようであればBottomSheetDialogを継承したクラスを用意することも検討する必要がありそうです。
ちなみにAlertDialogの表示位置を単純に下にすることは可能ですが動作的にはちょっと違ったものになってしまいます。

Swiftの場合

let alert: UIAlertController = UIAlertController(title: "タイマー設定", message: "時間を選択してください", preferredStyle:  UIAlertControllerStyle.actionSheet)

let action: ((UIAlertAction) -> Swift.Void) = {
    (action: UIAlertAction!) -> Void in
    var title: [String] = (action.title?.characters.split(separator: ":").map(String.init))!
    self.defaultTimerCount = Int64(title[0])! * 60
    self.initTimer()
}

for i in 1...10 {
    alert.addAction(UIAlertAction(title: String.init(format: "%02d:00", i), style: UIAlertActionStyle.default, handler: action))
}
alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel))

present(alert, animated: true, completion: nil)

iOSには標準でモーダルなダイアログの表示設定があるので特殊な実装の必要がありません。
クラスが別れないのでグローバル変数やメソッドを直接呼ぶこともできるのでAndroidのようにキャストが必要になったりせずスッキリしています。

おまけ

func changeBtnText(btn: UIButton, text: String) {
    UIView.setAnimationsEnabled(false)
    btn.setTitle(text, for: UIControlState.normal)
    btn.layoutIfNeeded()
    UIView.setAnimationsEnabled(true)
}

iOSではLabelにアクションをつけることができなかったのでタイマーの時間表示をするラベルをボタンで実装しました。
このボタンの文言を変える時にViewがちらつくという問題が発生したので上記のコードで対応しています。

まとめ

巷ではKotlinとSwiftは似ていると聞くことがあったのでSwift簡単に書けるんじゃないかと甘く見ていたらそれなりに痛い目にあいました。
痛い目にあった大部分はAndroidStudioとXCodeの差によるものなので、Swift自体はObjctive-Cに比べればだいぶ学習コストが低いようには感じます。
AndroidもSwiftで書けるようになるという噂も聞きますが、AndroidとiOSの思想の違いは大きいと思うのでSwiftで書けるからiOSエンジニアでもAndroidアプリが簡単に書ける!ということにはなかなかならなそうです。

結論

KotlinとSwiftは似てません!

17
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?