20210124のSwiftに関する記事は12件です。

ちゃんと紐づけてるのにunrecognized selector sent to instanceエラーが出る時の対処方法

このエラーに数日悩まされたので備忘録として残します。

Bar Button Itemタップ時の処理を実装していたところ、
以前までは正常に実装できていたものが、「unrecognized selector sent to instance」のエラーで前に進めなくなった。

調べてみると、いくつか要因があるとのこと

  1. identity inspectorのClassの設定が間違っている
  2. Inherit Module From Targetにチェックが入っていない(自分はこれが原因だった)
  3. タップ時のアクションを示す関数にて、引数(例:_ sender: UIBarButtonItem)が設定されていない

(2が原因の場合、Inherit Module From Targetにチェックを入れるか、もしくは、Moduleに手打ちすればよいとのこと)

参考になればいいな

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

【Swift】UITextFieldのキーボードを閉じる方法

UITextFieldに入力時、キーボードタイプをNumber PadにするとReturnキーが無くてtextFieldShouldReturnで閉じることが出来なくて困ったのでキーボードを閉じる方法を調べました。

キーボードを閉じる方法 その①

UITextFieldDelegatetextFieldShouldReturnにキーボードを閉じる処理を書いて、キーボードのリターンキーを押すとキーボードが閉じてくれる方法

 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

その方法で困った事

キーボードタイプがNumber Padだとリターンキーが無くて困った。
textFieldShouldReturnが使えないじゃないか、、、

numberpadWithDone

キーボードを閉じる方法 その②

InputAccesoryViewに完了ボタンを追加して、ボタンをトリガーにしてキーボードを閉じてもらう方法

numberpad

こんな感じでキーボード上に完了ボタンを出現させる。

@IBOutlet weak var numberPadTextField: UITextField!

override func viewDidLoad() {
        super.viewDidLoad()

        //inputAccesoryViewに入れるtoolbar
        let toolbar = UIToolbar()

        //完了ボタンを右寄せにする為に、左側を埋めるスペース作成
        let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
                                    target: nil,
                                    action: nil)
        //完了ボタンを作成
        let done = UIBarButtonItem(title: "完了",
                                   style: .done,
                                   target: self,
                                   action: #selector(didTapDoneButton))

        //toolbarのitemsに作成したスペースと完了ボタンを入れる。実際にも左から順に表示されます。
        toolbar.items = [space, done]
        toolbar.sizeToFit()

        //作成したtoolbarをtextFieldのinputAccessoryViewに入れる
        numberPadTextField.inputAccessoryView = toolbar

        //キーボードタイプを番号のみに指定
        numberPadTextField.keyboardType = .numberPad
    }

    //完了ボタンを押した時の処理
    @objc func didTapDoneButton() {
        numberPadTextField.resignFirstResponder()
    }

キーボードを閉じる方法 その③

ViewControllerにTap Gesture Recognizerを配置して、画面をタップされたらキーボードを閉じる方法

1.Tap Gesuture RecognizerViewControllerに配置

tapgesture

2.Tap Gesuture Recognizercontrol + ドラッグでViewControllerと紐付け

tapgesture2

3.紐付けしたTap Actionにキーボードを閉じる処理を書く

@IBAction func didTapView(_ sender: UITapGestureRecognizer) {
        //キーボードを閉じる処理
        view.endEditing(true)
    }

なんでview.endEditing(true)がキーボードを閉じてくれるのか?

公式ドキュメントより引用

Causes the view (or one of its embedded text fields) to resign the first responder status.

ビュー(またはその埋め込みテキストフィールドの1つ)にファーストレスポンダーステータスを辞任させます。

ということでview.endEditing()の引数にtrueを入れると、キーボードが現れている場合はtextField.resignFirstResponder()してくれるのでキーボードを閉じてくれます。

キーボードを閉じる方法 その④

その③の画面タップという方法は同じですが、実装がスマートなパターン

Tap Gesture Recognizerを配置したり、ファイルに紐付けたりしなくて良いです。

これを宣言するだけ。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }

サンプルデモ

キーボード上のinputAccesoryViewの完了ボタンと画面タップでキーボードを閉じています。
イメージ.GIF

まとめ

些細なことから調べ出したら色んな方法を知ることが出来た。
これも参考にさせてもらった記事のおかげです。
ありがとうございました。
是非、参考元の記事も見に行ってみてください☺︎

また何か間違いやより良い方法がありましたら、優しく教えていただけると幸いです?‍♂️

参考

UTextFieldのソフトキーボードを数値のみにして完了ボタンを追加する
[Swift]キーボード以外をタッチするとキーボードが下がる方法
Apple公式ドキュメント: endEditing(_:)

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

AppStoreConnectで連絡先情報が保存されない

はじめに

AppStoreにアプリを出すとき、連絡先情報が保存されない状態が2週間ぐらい続き、また対処法がなかったため(Appleに問い合わせても解決しなかった)ため、ここに対処法を示します。

エラー内容

AppReviewに関する情報のサイン情報、連絡先情報を入れ、右上の保存を押したところ、保存中のまま更新されない状態になった

S__83935236.jpg

エラー修正方法

電話番号を080~と書いていたところ、+81 080~と書くことで保存ができるようになった。
その他の部分はまだ変更をしなくても問題ありません。

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

[iOS] git pushでLintチェックを行う(Git Hooks + SwiftLint)

こんにちは。都内でiOSエンジニアをしている @zrn-ns です。

弊社のプロジェクトではSwiftLintを利用しており、最低限のコーディング規約についてはSwiftLintでチェックする事ができるようになっています。

SwiftLintはビルドフェーズに含めることも想定されており、公式のガイドに従うだけでビルド時にLintチェックを行い、規約に沿った記述を徹底させることができます。

しかし今回諸事情があり、ビルドフェーズではなく、Gitでリモートリポジトリにpushする直前でLintチェックを行うようにしてみました。

なぜビルドフェーズではなく、Push時にチェックするのか

ビルドフェーズで実行する方式だとビルド時に多少のオーバーヘッドが発生してしまいます(差分チェックを行ってくれるようなので、ほぼ気にならないレベルですが)。
また弊社社内ではXcode派AppCode派が存在します。AppCodeではSwiftLintのプラグインが存在するため、Lintチェックをビルドフェーズに含める方式だと、2重でLintチェックが行われてしまうことになります。

これらの問題を解消するため、ビルドフェーズでLintチェックを行うのをやめ、リモートリポジトリにコードをpushするタイミングでLintチェックを行うようにしてみました。

どうやってPush時に任意のスクリプトを実行するのか

Gitには、各アクション(commit, push, merge等)の実行の前後に任意のスクリプトを実行するためのGit Hooksという仕組みがあります。

Git Hooksでは、commitやpush, mergeの前後のタイミングで、各Gitリポジトリの.git/hooks/ディレクトリ下に配置したスクリプトが自動的に実行されます。
例えば、pushの直前でスクリプトを実行したい場合、.git/hooks/pre-pushというスクリプトが存在すれば、それが自動的に呼び出されます。

Git Hooksを利用して、push時にSwiftLintを実行する

今回はGit Hooksの設定を全メンバー間で共有できるようにし、全メンバーがpush前にLintチェックを行うことを強制できるようにします。

スクリプトの作成

.git/ ディレクトリ配下はgitで管理されない(コミットできない)ため、.git/hooks/pre-push にスクリプトを記述するとメンバー間で設定を共有することができません。

そこで、git管理下にGit Hooks用のスクリプトを入れておくための専用のディレクトリを作成し、そこに配置したpre-pushスクリプトを参照するようにgitの設定を変更します。

cd {プロジェクトディレクトリ}

# git hooksを入れるためのディレクトリとスクリプトを作成
mkdir .githooks
touch .githooks/pre-push

# スクリプトに実行権限を与える
chmod 755 .githooks/pre-push

場合によってはLintチェックを無視してpushしたい場合もあるかと思うので、Lintチェックでエラーがあった場合、pushを続行するか選択できるようにします。

pre-pushのスクリプトの中身は下記のようにします。(※swiftlintのバイナリへのパスは適宜書き換えてください)

.git/hooks/pre-push
#!/bin/bash

set -eu
cd `dirname $0`

echo -n 'Linting... '

lint_result=$(swiftlint --reporter emoji --quiet --path ../ --config ../.swiftlint.yml)

if [ -n "$lint_result" ]; then
  # Lintで警告が見つかった場合、push操作を続けるかユーザに判断させる
  echo 'Some issues found.'
  echo '====================================='
  echo "$lint_result" | sed -e "s/^/>> /"
  echo '====================================='
  echo ''
  echo -n 'Push anyway?[y/N]: '

  exec < /dev/tty
  read YN

  if [ "$YN" = "y" ]; then
    echo 'Ok. continue.'
  else
    echo 'Push aborted.';
    exit 1;
  fi
else
  echo 'No issues found!'
fi

exit 0

最後に、Git Hooksの読み込み先ディレクトリを変更します。
この設定は各自の環境で実行する必要があるので、環境構築手順書に追加するのが良いかと思います

# Git Hooksのディレクトリを.githooksディレクトリに変更
git config --local core.hooksPath .githooks/

動作確認

実際に動作させてみた結果は下記のような感じになります。

規約違反なし

% git push origin HEAD
Linting... No issues found!
Enumerating objects: 33, done.
Counting objects: 100% (33/33), done.
Delta compression using up to 8 threads
Compressing objects: 100% (27/27), done.
Writing objects: 100% (33/33), 8.73 KiB | 2.18 MiB/s, done.
Total 33 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:zrn-ns/swiftlint_on_pre_push.git
 * [new branch]      HEAD -> main

%

規約違反あり -> push続行

% git push origin HEAD
Linting... Some issues found.
=====================================
>> /path/to/project/AppDelegate.swift
>> ⚠️ Line 36: Files should have a single trailing newline.
>> ⚠️ Line 34: Limit vertical whitespace to a single empty line. Currently 2.
>> /path/to/project/SwiftLintOnPrePush/SceneDelegate.swift
>> ⚠️ Line 52: Files should have a single trailing newline.
>> ⚠️ Line 50: Limit vertical whitespace to a single empty line. Currently 2.
=====================================

Push anyway?[y/N]: y
Ok. continue.
Enumerating objects: 33, done.
Counting objects: 100% (33/33), done.
Delta compression using up to 8 threads
Compressing objects: 100% (27/27), done.
Writing objects: 100% (33/33), 8.73 KiB | 2.18 MiB/s, done.
Total 33 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:zrn-ns/swiftlint_on_pre_push.git
 * [new branch]      HEAD -> main

%

規約違反あり -> push中止

% git push origin HEAD
Linting... Some issues found.
=====================================
>> /path/to/project/AppDelegate.swift
>> ⚠️ Line 36: Files should have a single trailing newline.
>> ⚠️ Line 34: Limit vertical whitespace to a single empty line. Currently 2.
>> /path/to/project/SwiftLintOnPrePush/SceneDelegate.swift
>> ⚠️ Line 52: Files should have a single trailing newline.
>> ⚠️ Line 50: Limit vertical whitespace to a single empty line. Currently 2.
=====================================

Push anyway?[y/N]: n
Push aborted.
error: failed to push some refs to 'git@github.com:zrn-ns/swiftlint_on_pre_push.git'

%

サンプルプロジェクト

サンプルプロジェクトはこちらにアップしました。

FIXME

今回の手法では、Git Hooksのディレクトリをgit管理下に置くことで、全メンバーがスクリプトを共有する方法をとっています。
この方法は、スクリプトに修正が入った際にも環境間の同期が取れて便利な半面、各メンバーが独自のGit Hooksスクリプトを配置できなくなってしまいます
(masterへの直pushを防止したりするためにもGit Hooksは便利に使えるので、個人でスクリプトを配置したい場面は多いと思います)

何か良いアイディアがあれば教えていただけると幸いです?

まとめ

push前にSwiftLintのチェックを行う方法を解説しました。

この方法を使えば、各ビルドごとにLintチェックのオーバーヘッドがかかることを防げますし、PullRequestの作成前に問題に気づくことができるので、PullRequest作成後にコードを微調整するコストをかなり減らせるはずです。

ただし、新規メンバーが参入した場合には、ビルドごとにLintチェックを掛けてあげたほうが定着は早そうなので、その際はビルドフェーズに追加する方法でLintチェックを行ったほうが良いのかなと思います。

謝辞

下記のサイトを参考にさせていただきました。

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

GitHooksを利用し、リモートリポジトリにPushするときにSwiftLintを実行する

こんにちは。都内でiOSエンジニアをしている @zrn-ns です。

弊社のプロジェクトではSwiftLintを利用しており、最低限のコーディング規約についてはSwiftLintでチェックする事ができるようになっています。

SwiftLintはビルドフェーズに含めることも想定されており、公式のガイドに従うだけでビルド時にLintチェックを行い、規約に沿った記述を徹底させることができます。

しかし今回諸事情があり、ビルドフェーズではなく、Gitでリモートリポジトリにpushする直前でLintチェックを行うようにしてみました。

なぜビルドフェーズではなく、Push時にチェックするのか

ビルドフェーズで実行する方式だとビルド時に多少のオーバーヘッドが発生してしまいます(差分チェックを行ってくれるようなので、ほぼ気にならないレベルですが)。
また弊社社内ではXcode派AppCode派が存在します。AppCodeではSwiftLintのプラグインが存在するため、Lintチェックをビルドフェーズに含める方式だと、2重でLintチェックが行われてしまうことになります。

これらの問題を解消するため、ビルドフェーズでLintチェックを行うのをやめ、リモートリポジトリにコードをpushするタイミングでLintチェックを行うようにしてみました。

どうやってPush時に任意のスクリプトを実行するのか

Gitには、各アクション(commit, push, merge等)の実行の前後に任意のスクリプトを実行するためのGit Hooksという仕組みがあります。

Git Hooksでは、commitやpush, mergeの前後のタイミングで、各Gitリポジトリの.git/hooks/ディレクトリ下に配置したスクリプトが自動的に実行されます。
例えば、pushの直前でスクリプトを実行したい場合、.git/hooks/pre-pushというスクリプトが存在すれば、それが自動的に呼び出されます。

Git Hooksを利用して、push時にSwiftLintを実行する

今回はGit Hooksの設定を全メンバー間で共有できるようにし、全メンバーがpush前にLintチェックを行うことを強制できるようにします。

スクリプトの作成

.git/ ディレクトリ配下はgitで管理されない(コミットできない)ため、.git/hooks/pre-push にスクリプトを記述するとメンバー間で設定を共有することができません。

そこで、git管理下にGit Hooks用のスクリプトを入れておくための専用のディレクトリを作成し、そこに配置したpre-pushスクリプトを参照するようにgitの設定を変更します。

cd {プロジェクトディレクトリ}

# git hooksを入れるためのディレクトリとスクリプトを作成
mkdir .githooks
touch .githooks/pre-push

# スクリプトに実行権限を与える
chmod 755 .githooks/pre-push

場合によってはLintチェックを無視してpushしたい場合もあるかと思うので、Lintチェックでエラーがあった場合、pushを続行するか選択できるようにします。

pre-pushのスクリプトの中身は下記のようにします。(※swiftlintのバイナリへのパスは適宜書き換えてください)

.git/hooks/pre-push
#!/bin/bash

set -eu
cd `dirname $0`

echo -n 'Linting... '

lint_result=$(swiftlint --reporter emoji --quiet --path ../ --config ../.swiftlint.yml)

if [ -n "$lint_result" ]; then
  # Lintで警告が見つかった場合、push操作を続けるかユーザに判断させる
  echo 'Some issues found.'
  echo '====================================='
  echo "$lint_result" | sed -e "s/^/>> /"
  echo '====================================='
  echo ''
  echo -n 'Push anyway?[y/N]: '

  exec < /dev/tty
  read YN

  if [ "$YN" = "y" ]; then
    echo 'Ok. continue.'
  else
    echo 'Push aborted.';
    exit 1;
  fi
else
  echo 'No issues found!'
fi

exit 0

最後に、Git Hooksの読み込み先ディレクトリを変更します。
この設定は各自の環境で実行する必要があるので、環境構築手順書に追加するのが良いかと思います

# Git Hooksのディレクトリを.githooksディレクトリに変更
git config --local core.hooksPath .githooks/

動作確認

実際に動作させてみた結果は下記のような感じになります。

規約違反なし

% git push origin HEAD
Linting... No issues found!
Enumerating objects: 33, done.
Counting objects: 100% (33/33), done.
Delta compression using up to 8 threads
Compressing objects: 100% (27/27), done.
Writing objects: 100% (33/33), 8.73 KiB | 2.18 MiB/s, done.
Total 33 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:zrn-ns/swiftlint_on_pre_push.git
 * [new branch]      HEAD -> main

%

規約違反あり -> push続行

% git push origin HEAD
Linting... Some issues found.
=====================================
>> /path/to/project/AppDelegate.swift
>> ⚠️ Line 36: Files should have a single trailing newline.
>> ⚠️ Line 34: Limit vertical whitespace to a single empty line. Currently 2.
>> /path/to/project/SwiftLintOnPrePush/SceneDelegate.swift
>> ⚠️ Line 52: Files should have a single trailing newline.
>> ⚠️ Line 50: Limit vertical whitespace to a single empty line. Currently 2.
=====================================

Push anyway?[y/N]: y
Ok. continue.
Enumerating objects: 33, done.
Counting objects: 100% (33/33), done.
Delta compression using up to 8 threads
Compressing objects: 100% (27/27), done.
Writing objects: 100% (33/33), 8.73 KiB | 2.18 MiB/s, done.
Total 33 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:zrn-ns/swiftlint_on_pre_push.git
 * [new branch]      HEAD -> main

%

規約違反あり -> push中止

% git push origin HEAD
Linting... Some issues found.
=====================================
>> /path/to/project/AppDelegate.swift
>> ⚠️ Line 36: Files should have a single trailing newline.
>> ⚠️ Line 34: Limit vertical whitespace to a single empty line. Currently 2.
>> /path/to/project/SwiftLintOnPrePush/SceneDelegate.swift
>> ⚠️ Line 52: Files should have a single trailing newline.
>> ⚠️ Line 50: Limit vertical whitespace to a single empty line. Currently 2.
=====================================

Push anyway?[y/N]: n
Push aborted.
error: failed to push some refs to 'git@github.com:zrn-ns/swiftlint_on_pre_push.git'

%

サンプルプロジェクト

サンプルプロジェクトはこちらにアップしました。

FIXME

今回の手法では、Git Hooksのディレクトリをgit管理下に置くことで、全メンバーがスクリプトを共有する方法をとっています。
この方法は、スクリプトに修正が入った際にも環境間の同期が取れて便利な半面、各メンバーが独自のGit Hooksスクリプトを配置できなくなってしまいます
(masterへの直pushを防止したりするためにもGit Hooksは便利に使えるので、個人でスクリプトを配置したい場面は多いと思います)

何か良いアイディアがあれば教えていただけると幸いです?

まとめ

push前にSwiftLintのチェックを行う方法を解説しました。

この方法を使えば、各ビルドごとにLintチェックのオーバーヘッドがかかることを防げますし、PullRequestの作成前に問題に気づくことができるので、PullRequest作成後にコードを微調整するコストをかなり減らせるはずです。

ただし、新規メンバーが参入した場合には、ビルドごとにLintチェックを掛けてあげたほうが定着は早そうなので、その際はビルドフェーズに追加する方法でLintチェックを行ったほうが良いのかなと思います。

謝辞

下記のサイトを参考にさせていただきました。

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

【SwiftUI】opacityの謎

はじめに

SwiftUIではViewに対して.opacity(Double)で不透明度を指定することができます。
引数Doubleの値が0で透明、1で不透過、0.5では50%の透過度ということになります。

公式ドキュメント
https://developer.apple.com/documentation/swiftui/view/opacity(_:)

ただ、透明の時は、タップした時に動作させるonTapGestureが反応しなくなることを発見しました。
その存在が消されてしまうのでしょうか?

検証

スライダーでサンプルを作成し実験してみました。
ボタンを押す男性をタップすると、上の表示がON/OFF切り替わるサンプルです。
このサンプルは次のような動きを示しました。

  • opacityが0以外の時はON/OFFが切り替わる
  • opacityが0の時はON/OFFが切り替わらない

ezgif.com-video-to-gif.gif

以下、ソースコード

ContentView.swift
struct ContentView: View {
  @State private var opacity: Double = 1.0
  @State private var isOn: Bool = false

  var body: some View {
    VStack {
      Spacer()

      Text(isOn ? "ON" : "OFF")
        .font(.title)
        .fontWeight(.semibold)
        .frame(width: 200, height: 100)
        .background(isOn ? Color.red : Color.green)
        .foregroundColor(isOn ? .white : .black)

      Spacer()

      Image("tapButtonImage")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .opacity(opacity)
        .frame(width: 200)
        .onTapGesture {
          isOn.toggle()
        }

      Slider(value: $opacity)
      Text("opacity: \(opacity)")

      Spacer()
    }
    .padding()
  }
}

opacityが0の時は、画像の存在そのものが消されている?とも考えましたが、画像が存在しなくなったら、スペースが詰められ、以下のような配置になるはずです。
なので枠として存在することになっています。

スクリーンショット 2021-01-24 14.09.05.png   スクリーンショット 2021-01-24 14.33.38.png

タップ判定させたい場合

ちなみに、透明でもタップ判定できないかと調べていたところ、contentShapeを使うと反応することが分かりました。
以下、contentShapeを追加したコードです。

ContentView.swift
Image("tapButtonImage")
  .resizable()
  .aspectRatio(contentMode: .fit)
  .opacity(opacity)
  .frame(width: 200)
  // contentShape追加
  .contentShape(Rectangle())
  .onTapGesture {
    isOn.toggle()
  }

ezgif.com-video-to-gif-3.gif

こちらを参考にしました。
https://stackoverflow.com/questions/57258371/swiftui-increase-tap-drag-area-for-user-interaction

透明でもタップ判定させたい場合、contentShapeを使えば、願う動作を実装できます。

おわりに

透明部分をタップして動作させたい!と思いopacityを0にしてコードを書いたのがこの現象に気づくきっかけでした。
引数を0.00001など限りなく0に近づければ、人間には透明に見えるが、機械からは透明ではないと判定されるので、問題ないっちゃ問題ないのですが。。
ですが、contentShapeという解決策も見つかったので、もし同じような現象で苦しんでいる方の参考になれば幸いです。

最後まで読んでくださりありがとうございました。

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

競プロで使える便利なエクステンション一覧(Swift)

私は競プロの記事をいくつか投稿しています。

オススメの読み方は、まず自分で考えることです。
ちょっとえらそうですが、自分で武器を増やしていくのが競プロの面白さのひとつだと思っています。
どうしてもわからないときに、私の記事が参考になると嬉しいです :relaxed:

はじめに

本記事は Swift/Kotlin愛好会 Advent Calendar 2020 の11日目の記事です。
空いているので埋めました。

競プロで使える便利な拡張メソッドとコンピューテッドプロパティを紹介します。

注意

  • 本記事で紹介しているソースコードが正しいかは保証できていません。
  • 本記事は随時更新予定です。

環境

  • OS:macOS Big Sur 11.1
  • Xcode:12.3 (12C33)
  • Swift:5.3.2
    5.2.1(2021/01/24現在のAtCoder)でも動作する

競プロで使える便利なエクステンション一覧

桁分割

説明

数値の桁を分割し、桁数の降順に整列した配列で返します。

ソースコード
private extension Numeric where Self: LosslessStringConvertible {
    var digits: [Int] { string.digits }
}
private extension LosslessStringConvertible {
    var string: String { String(self) }
}
private extension StringProtocol {
    var digits: [Int] { compactMap { $0.wholeNumberValue } }
}

出力例
1.digits -> [1]
32.digits -> [3, 2]
445.digits -> [4, 4, 5]
62096.digits -> [6, 2, 0, 9, 6]

参考リンク

合計

TBD

Decimal→Int変換

説明

DecimalInt に変換します。

私は pow() の戻り値を Int で欲しいときに使います。

ソースコード
import Foundation

private extension Decimal {
    var intValue: Int { NSDecimalNumber(decimal: self).intValue }
}

出力例
pow(2, 3).intValue -> 8
pow(10, 6).intValue -> 1000000

参考リンク

10進数→2進数変換

説明

10進数の数値を2進数の文字列に変換します。
digit に必要十分な大きさの値を渡さないとオーバーフローするので注意です。

まだAtCoderでは使ったことがありません。

ソースコード
private extension FixedWidthInteger {
    func binaryString(digit: Int) -> String {
        var result: [String] = []
        for i in 0..<(Self.bitWidth / 8) {
            let byte = UInt8(truncatingIfNeeded: self >> (i * 8))
            let byteString = String(byte, radix: 2)
            let padding = String(repeating: "0", count: 8 - byteString.count)
            result.append(padding + byteString)
        }
        return String(result.reversed().joined().suffix(digit))
    }
}

出力例
0.binaryString(digit: 2) -> "00"
1.binaryString(digit: 2) -> "01"
2.binaryString(digit: 4) -> "0010"
8.binaryString(digit: 8) -> "00001000"
255.binaryString(digit: 8) -> "11111111"
256.binaryString(digit: 8) -> "00000000" // !!!: オーバーフロー
256.binaryString(digit: 12) -> "000100000000"

参考リンク

おわりに

以上、 Swift/Kotlin愛好会 Advent Calendar 2020 の11日目の記事でした。

参考リンク

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

【競プロ日記/Swift】深さ優先探索(部分和問題)

部分和問題

問題

配列A[A1、A2………An]が与えられる。その配列内からいくつか選択肢、その和がkと等しいか判定せよ。
※制約
・1 <= n <= 20
・-10⁸ <= a(i乗) <= 10⁸
・-10⁸ <= k <= 10⁸

解答

//以下入力値例
let array = [2,3,8]
let sumValue = 18

func equalToSumValue(count: Int, sum: Int) -> Bool
{
    // 選択管理
    if count == array.count {
        print("count:\(count),sum:\(sum)")
        return sum == sumValue
    }

    // 値未選択時
    if equalToSumValue(count: count + 1, sum: sum) {
        return true;
    }

    // 値選択時
    if equalToSumValue(count: count + 1, sum: sum + array[count]) {
        return true;
    }
    return false
}

func solve()
{
    if equalToSumValue(count: 0, sum: 0) {
        print("correct")
    } else {
        print("incorrect")
    }
}
solve()

//出力結果
//count:3,sum:0
//count:3,sum:8
//count:3,sum:3
//count:3,sum:11
//count:3,sum:2
//count:3,sum:10
//count:3,sum:5
//count:3,sum:13
//incorrect

解説

上記はbit全検索を再帰関数を用いて行う処理になります。
図で表すと下記のような流れで、全検索していきます。処理の流れとしては配列の後ろの値から選択していくので、図の一番下から上に向かって処理が行われていきます。
20200105170127.png

参考

https://drken1215.hatenablog.com/entry/2020/01/05/185000
https://qiita.com/drken/items/e77685614f3c6bf86f44

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

【iOS】Firebaseど素人のFirebase入門 Firestore編

【iOS】Firebaseど素人のFirebase入門 認証編に続き、今回はFirestoreを学びながらサンプルアプリ作りに挑戦してみました。

作ったサンプルアプリのデモ

イメージ.GIF

Firestore上のデータがTableView上に表示されるアプリです。

Set Buttonを押すと、Firestoreに情報が書き込み or 更新(setdata)されます。
Add Buttonを押すと、Firestoreに情報が追加(addDocument)されます。
Firestoreからドキュメントを取得を押すを押すと、Firestoreに保存されている情報を取得(getDocuments)して、TableViewに反映します。

リアルタイムアップデートスイッチをOnにすると、Firestore上の情報が更新される度に自動的にTextViewを更新します。

まずはFirebaseに登録

1.Firebase公式ドキュメントに沿って、Firebase登録とFirebase SDKをアプリに追加する。
Firebase を iOS プロジェクトに追加する

2.Firebaseに追加したアプリのコンソールからCloud Firestoreを使用できるように設定する

AppDelegateでFirestoreの初期化

AppDelegate.swiftにFirebaseをインポート

AppDelegate.swift
import Firebase

didFinishLaunchingWithOptions内でFirestoreの初期化

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

        FirebaseApp.configure()
        let db = Firestore.firestore()
        print(db)

        return true
    }

ViewControllerにもFirebaseをインポート

作成を進めるファイルにもFirebaseをインポートする。

FirestoreViewController.swift
import Firebase

データの書き込み or 更新

データの書き込み or 更新にはsetData(documentData: [String : Any], completion:)を使用します。

db.collection("users").document("HiroshiTachi").setData([
                "name": name,
                "age": age,
            ]) { error in
                if let error = error {
                    print("ドキュメントの書き込みに失敗しました:", error)
                } else {
                    print("ドキュメントの書き込みに成功しました")
                }
            }

setDataでの書き込みの場合は、ドキュメント内に何もデータがない場合は新規作成を行い、すでにデータがある場合はそのデータの中身を更新します。

データの追加

データの追加にはaddDocument(data: , completion: )を使用します。

var ref: DocumentReference? = nil
ref = db.collection("users").addDocument(data:[
                "name": name,
                "age": age,
            ]) { error in
                if let error = error {
                    print("ドキュメントの追加に失敗しました:", error)
                } else {
                    print("ドキュメントの追加に成功しました:", ref?.documentID as Any)
                }
            }

こちらはsetDataとは違い、指定したcollection内に問答無用でどんどん新規追加してくれます。
なので、データを更新したりするのには使用出来ません。

データを1回取得

データを1回取得するには、getDocuments()を使用します。

db.collection("users").getDocuments() { (querySnapshot, error) in
                if let error = error {
                    print("ドキュメントの取得に失敗しました:", error)
                } else {
                    print("ドキュメントの取得に成功しました")
                    for document in querySnapshot!.documents {
                        let data = document.data()
                        //取得したデータに対しての処理を書く
                        print(data["name"])
                        }
                    }
                }
            }

データをリアルタイムで取得する

データが更新される度にリアルタイム取得をするには、addSnapshotListener(listener:)を使用します。

1.グローバル変数としてリスナーListenerRegistrationを定義

var listener: ListenerRegistration?

2.リスナーをアタッチ

listener = db.collection("users").addSnapshotListener { documentSnapshot, error in
                if let error = error {
                    print("ドキュメントの取得に失敗しました", error)
                } else {
                    self.queriedDataArray = []
                    if let documentSnapshots = documentSnapshot?.documents {
                        for document in documentSnapshots {
                            let data = document.data()
                            //アップデートされた際に行いたい処理を書く
                            print(data["name"])
                                }
                            }
                        }
                    }
                }
            }

3.リスナーを切り離し

リスナーは必要がなくなったら切り離し(デタッチ)しましょう。

listener?.remove()

公式ドキュメントより

データをリッスンする必要がなくなったら、イベント コールバックが呼び出されないようにリスナーをデタッチしなければなりません。これにより、クライアントは更新を受信するための帯域幅の使用を停止できます。

まとめ

公式ドキュメントの情報は本当に充実しているので、この記事より遥かに充実して間違いのない情報が得られますので、参考にする際は是非とも公式ドキュメントを参考にしていただければと思います。笑

また、セキュリティルールについてはまだまだなので知識を深めていきたい。

何か間違いがありましたら、優しく訂正していただけると幸いです?‍♂️

参考

Cloud Firestore を使ってみる
Cloud Firestore にデータを追加する
Cloud Firestore でデータを取得する
Cloud Firestore でリアルタイム アップデートを入手する

サンプルアプリのコード全体

FirestoreViewController.swift
import UIKit
import Firebase

class FirestoreViewController: UIViewController {

    @IBOutlet weak var queryDataTableView: UITableView!

    @IBOutlet weak var setTextLabel: UILabel!
    @IBOutlet weak var setAgeTextField: UITextField!
    @IBOutlet weak var setButton: UIButton!

    @IBOutlet weak var addTextField: UITextField!
    @IBOutlet weak var addAgeTextField: UITextField!
    @IBOutlet weak var addButton: UIButton!

    @IBOutlet weak var getButton: UIButton!
    @IBOutlet weak var realTimeQuerySwitch: UISwitch!

    let db = Firestore.firestore()
    var queriedDataArray = [String()]

    var listener: ListenerRegistration?

    override func viewDidLoad() {
        super.viewDidLoad()

        queryDataTableView.dataSource = self
        addButton.layer.cornerRadius = 15
        setButton.layer.cornerRadius = 15
        getButton.layer.cornerRadius = 15
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        listener?.remove()
    }

    @IBAction func didTapButton(_ sender: UIButton) {

        switch sender.currentTitle {

        //SetButton Action
        case "Set":

            guard let name = setTextLabel.text, let age = setAgeTextField.text else { return }
            db.collection("users").document("HiroshiTachi").setData([
                "name": name,
                "age": age,
            ]) { error in
                if let error = error {
                    print("ドキュメントの書き込みに失敗しました:", error)
                } else {
                    print("ドキュメントの書き込みに成功しました")
                }
            }

        //AddButton Action
        case "Add":

            guard let name = addTextField.text, let age = addAgeTextField.text else { return }

            var ref: DocumentReference? = nil
            ref = db.collection("users").addDocument(data:[
                "name": name,
                "age": age,
            ]) { error in
                if let error = error {
                    print("ドキュメントの追加に失敗しました:", error)
                } else {
                    print("ドキュメントの追加に成功しました:", ref?.documentID as Any)
                }
            }

        //GetButton Action
        default:
            queriedDataArray = []
            db.collection("users").getDocuments() { (querySnapshot, error) in
                if let error = error {
                    print("ドキュメントの取得に失敗しました:", error)
                } else {
                    for document in querySnapshot!.documents {
                        let data = document.data()
                        guard let name = data["name"] as? String, let age = data["age"] as? String else {
                            return
                        }
                        let nameAndAge = name + " " + age + "歳"
                        self.queriedDataArray.append(nameAndAge)
                        DispatchQueue.main.async {
                            self.queryDataTableView.reloadData()
                        }
                    }
                }
            }
        }
    }

    @IBAction func didChangeRealTimeQueryState(_ sender: UISwitch) {

        if sender.isOn {
            print("リアルタイムアップデートOn")
            listener = db.collection("users").addSnapshotListener { documentSnapshot, error in
                if let error = error {
                    print("ドキュメントの取得に失敗しました", error)
                } else {
                    self.queriedDataArray = []
                    if let documentSnapshots = documentSnapshot?.documents {
                        for document in documentSnapshots {
                            let data = document.data()
                            if let name = data["name"] as? String, let age = data["age"] as? String {
                                let nameAndAge = name + " " + age + "歳"
                                self.queriedDataArray.append(nameAndAge)
                                DispatchQueue.main.async {
                                    self.queryDataTableView.reloadData()
                                }
                            }
                        }
                    }
                }
            }
        } else {
            print("リアルタイムアップデートOff")
            listener?.remove()
        }
    }
}

extension FirestoreViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return queriedDataArray.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = queryDataTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = queriedDataArray[indexPath.row]
        return cell
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Storyboardを使わずにコードベースでプロジェクトを作成する方法

はじめに

iOSアプリを複数人で開発する際、Storyboardを使用すると、ソースコードに意図せぬ差分が出やすく管理がめんどくさい・・・。
だったらUIKitじゃなくてSwiftUI使いましょう

ということで、Storyboardを使わずにコードベースで開発する方法をまとめます。

なお、iOS13以降とそれ以前では設定の方法が大きく異なります。
今記事では、iOS14での設定方法をまとめます。

バージョン情報

Xcode 12.3
Swift 5.3.2

プロジェクト作成

1.Xcodeを起動してプロジェクトを作成

通常時と同様の手順でプロジェクトを作成します。

  1. [Create a new Xcode project]
  2. [Single View App]を選択して[Next]
  3. 任意のプロジェクト名を入力
  4. プロジェクト設定
    1. Interface --> Storyboard
    2. Life Cycle --> UIKit
    3. Language --> Swift
  5. プロジェクトを保存

2.Storyboardの削除

上記の手順で作成したプロジェクトからStoryboardを消していきます

  1. Main.Storyboardを削除。

  2. Info.plistの項目[Storyboard Name]を -ボタンで削除
    説明1

  3. [プロジェクト]>TARGETS>ProjectName>General>Development Info>Main Interfaceを空白にする。
    説明2

  4. SceneDelegate.swiftに初期画面情報を設定

以下のように編集。

SceneDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        if let windowScene = scene as? UIWindowScene{
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = ViewController.init()
            self.window = window
            window.makeKeyAndVisible()
        }
    }

説明3

3.ビルド

  1. ViewController.swiftを以下のように編集
ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red

        let titleLabel: UILabel = {
            let view = UILabel.init()
            view.text = "はろーわーるど★"
            view.textColor = .yellow
            view.translatesAutoresizingMaskIntoConstraints = false
            return view
        }()

        view.addSubview(titleLabel)

        NSLayoutConstraint.activate([
            titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),
            titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
        ])
    }


}

説明4

目に悪そうな色ですが、意図通りです。
説明5

おわりに

今回は、iOS14での、Storyboardを使用しないプロジェクトの作成〜ビルドまでをまとめました。
コードでAutoLayoutを使用する場合は、以下のコードでAutoresizingを無効にする必要があリますのでお気をつけください。

ViewController.swift
view.translatesAutoresizingMaskIntoConstraints = false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift UI】ObservableObjectとObservedObjectを理解するための時計アプリ

この記事は何か

SwiftUIフレームワークによるアプリ開発におけるデータフローを理解するために、シンプルな時計アプリを作成します。

環境

macOS 11.1
Xcode 12.3
Swift 5.3

コード

アプリのエントリーポイント
@main
struct WatchApp: App {
    let clock = Clock()

    var body: some Scene {
        WindowGroup {
            ContentView(clock: clock)
        }
    }
}
ObservableObjectのクラス
class Clock: ObservableObject {
    var timer = Timer()
    @Published var currentTime = ""

    init() {
        start()
    }

    func start() {
        let formatter = DateFormatter()
        formatter.timeStyle = .medium
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (_) in
            self.currentTime = formatter.string(from: Date())
        })
    }

    func stop() {
        timer.invalidate()
    }
}
ContentView
import SwiftUI

struct ContentView: View {
    @ObservedObject var clock = Clock()

    var body: some View {
        Text("\(clock.currentTime)")
            .font(.title)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(clock: Clock())
        }
    }
}

スクリーンショット 2021-01-24 3.21.09.png

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

【iOS】個人開発で使って気分がよかったライブラリ5選【個人開発】

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