iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

【Swift】CropViewController を使ってみた

今日は、SwiftObjective-C で画像をトリミングする際に非常に有名なライブラリ、CropViewController を使ってみたいと思います📱

github.com

Swift 標準の UIImagePickerController クラスでもallowsEditingtrue にすることで、画像選択後にトリミングをすることはできるんですが、スクエアしかサポートされていないので異なるアスペクト比で画像をトリミングすることができません😢 (もし、標準でできるものがあれば是非教えてください)

ということで CropViewController を使ってみます。

それではやっていく

今回は SPM(Swift Package Manager) で導入していくので、まずは依存関係を追加します。

  1. File > Swift Packages > Add Package Dependency を選択します。

  2. プロジェクトを選択後に https://github.com/TimOliver/TOCropViewController.git を入力し、リポジトリを探します。

  3. 表示された依存関係を追加します。

  4. インストールが完了したので実際に使ってみます。

デフォルトだと左側の画像のようになります。今回はアスペクト比の指定のみできれば良かったので、他のボタンは非表示に設定しています。また、aspectRatioPreset を設定することでデフォルトのアスペクト比を設定することができるようです。

デフォルト カスタム
f:id:yum_fishing:20201227161458p:plain f:id:yum_fishing:20201227161515p:plain
let cropViewController = CropViewController(image: result.image)
cropViewController.delegate = self
cropViewController.aspectRatioPreset = .preset16x9
cropViewController.rotateButtonsHidden = true
cropViewController.cancelButtonTitle = "キャンセル"
cropViewController.doneButtonTitle = "完了"
cropViewController.children.first?.modalTransitionStyle = .coverVertical
self?.present(cropViewController, animated: true, completion: nil)

ちなみにこの設定は、CropViewController を閉じる時に元の画面が見えなくなってしまう問題を修正するために modalTransitionStyle.coverVertical にしています 🤔 スタックオーバーフローにも同様の問題があったのでリンクも載せておきます。

stackoverflow.com

cropViewController.children.first?.modalTransitionStyle = .coverVertical

という感じで今日は以上になります。それではまた明日🦅

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Xcode】〇〇 is not available. please reconnect the device の対処法

Xcode12.1 + iOS14.1 の実機で、いつものごとく実機にアプリをインストールしようとすると何やら見覚えのあるエラーが、😥

〇〇 is not available. please reconnect the device

Xcode11.xx 系では、iOS14 以降のペアリング暗号化の問題で、実機へのインストールが失敗するのは以前の記事に書いた通り認識していますが、今回は Xcode12.xx 系なので違いそうです。

yamato8010.hatenablog.com

解決法

アクセサリの繋ぎ直し、クリーン、Xcode の再起動などもろもろ試しましたがどれもダメで、結果 iPhone 側の再起動で無事にインストールができるようになりました👷‍♀️

Xcode12.1 は動作不安定なところも結構あるので、そろそろアップデートした方が良さそうですね〜 👀

という感じで本日も以上になります。それでは、また明日🦅

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Swift】ローカルプッシュを送信する

今日はタイトルの通り、Swift でローカルプッシュを送信する方法を紹介したいと思います👷‍♀️ ちなみに、実装は Swift5.3 で動作確認は iOS14.1 で行っておりやす。

それではやっていく🦅

まずは、通知を送信できるようにするためにユーザに許可してもらう必要があります。

    func requestAuthorizationForNotification() {
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in
            // リクエスト終了した時のハンドリング
        }
    }

requestAuthorizationcompletionHandler の第一引数、第二引数の内容は下記の通りになります。

  • granted

    • 一つ以上のオプションが許可されたかどうかの Boolean。オプションについては こちら を参照してください。
  • error

    • エラー情報を含むオブジェクト。

また、アプリがフォアグランド状態にある時もバックグランド時と同じように通知を表示させたいので AppDelegate で下記のようにコードを付け加えます。ちなみに、イベントを受け取るために UNUserNotificationCenter.current().delegate = self の設定は必要なので、起動時などに適所設定しましょう。

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.badge, .sound, .alert])
    }
}

次に、ローカルプッシュを送信するためのコードを書いていきます。下記がメソッドが呼ばれてから 5秒後 に通知を送信するサンプルコードです。

func sendNotificationAfterFiveSeconds() {
        let content = UNMutableNotificationContent()
        content.title = "これはタイトルです"
        content.body = "これはボディです"
        content.sound = UNNotificationSound.default

        let request = UNNotificationRequest(identifier: "fiveSeconds", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false))
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    }

UNMutableNotificationContent は、通知のタイトルやメッセージ、サウンドなどのコンテンツを設定するためのクラスです。ちなみに、userInfo などの通知のカスタム情報などもこのクラスで設定します。詳しくは下記を参照してみてください。

developer.apple.com

UNNotificationRequest では、その名の通り通知のリクエストを作成します。引数の identifier には通知の一意の識別子を、content には前述した通知のコンテンツを、trigger には通知を送信するトリガーをそれぞれ設定します。

トリガーには、主に下記の4つの種類がありますが、最後の UNPushNotificationTrigger はリモートプッシュ通知が送信される際にシステムによってインスタンスが生成されるので、ローカルで通知を送信する際は上3つがトリガーになります。また、即時通知を送信したい場合は trigger 引数を nill に設定します。

今回の場合だと、UNTimeIntervalNotificationTrigger を使用して 5秒後 に通知を送信できるようにしています。その他にも UNCalendarNotificationTrigger を使用すればイベントの予定日に通知を送ったり、UNLocationNotificationTrigger を使用することで特定の場所に到着した時に通知を送るなんてこともできます。

ちなみに、UNUserNotificationCenter.current().add で設定してリクエストで、既にスケジュールされている identifier があるものはスケジュールが上書きされます。

という感じで今日は以上になります🦅

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Android】Release バリアントでビルドした時の Error 対処法

先日、新規作成したアプリで試しにバリアントを release に設定し、ビルドしてみると下記のようなエラーが発生しました。

Error: The apk for your currently selected variant (app-release.apk) is not signed. Please specify a signing configuration for this variant (release).

どうやら、release バリアントを手元でビルドするには署名が必要みたいですね👀 ちなみに、debug に関しては Android Studio が自動で署名してくれてるらしい。

ということでrelease でビルドするためにやることは下記になります。

  1. Key Store の作成

  2. Signing Config の作成

  3. 2. で作成した Signing Configrelease バリアントに設定

1.2. に関しては、Android Studio 4.0.x 系でも下記の記事とほぼ同じなので割愛します🦅

qiita.com

3. については、2. で開いている File>Project Structure...Build Variants をまずは選択します。次に表示されているバリアントの中から release を選択し、Signing Config セクションで 2. で作成した Signing Config を設定し、Apply すれば完了です🎉

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Fastlane】metadata のアップロードでこける

App Store Connectメタデータをローカルで編集後、アップロードしようとすると下記のようなエラーが発生していました🤔

$ bundle exec fastlane metadata
...
The provided entity includes an attribute with a value that has already been used - The version number has been previously used.

Fastfile:

  desc "Upload metadata"
  lane :metadata do
    deliver(
      skip_screenshots: true,
      force: true
    )
  end

解決法

こちらを参考に、deliver のオプションに app_version を指定して実行したところ上手く動作するようになりました。

github.com

Fastfile:

  desc "Upload metadata"
  lane :metadata do
    deliver(
      app_version: app_version,
      skip_screenshots: true,
      force: true
    )
  end

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【リジェクト】Guideline 5.3.2 - Legal - Gaming, Gambling, and Lotteries

いつものごとくアプリの申請を出していたら、久しぶりにリジェクトされてしまいました🙄 既存機能でのリジェクトだったので、Apple の方が休暇前に本気でレビューしてくれたんでしょうね 、、

Guideline 5.3.2 - Legal - Gaming, Gambling, and Lotteries

Your app includes a contest or sweepstakes but it does not:

- Indicate that Apple is not involved in any way with the contest or sweepstakes.
- Enforce an app age rating of 17+.

解決法

ガイドライン 5.3.2 によると、アプリにコンテストやキャンペーンなどの機能が含まれている場合は、Apple がそれらの主催でないことを明記する必要があるらしいですね。

developer.apple.com

ということで、

  • Apple が主催でないことをアプリ上で明記
  • 年齢制限指定を 17+ に設定

することで、解決できそうです👷‍♀️

ちなみに 年齢制限指定 は、App Store Connect の該当するアプリのバージョンの編集ページにある App一般情報 セクションの 年齢制限指定 から下記のように編集することができます。ここで、ギャンブルおよびコンテストなし から まれ/軽度 に変更することで、年齢制限指定が 17+ に変更されます。

f:id:yum_fishing:20201223161828p:plain

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Javascript】Promise ってなんぞや

最近、個人で開発しているアプリで Node.js を触ることが多いのですが、Javascript で非同期処理を扱う際の知識が不足しているので、この記事をきっかけに少しずつ勉強していこうかと思います👷‍♀️

ということでまずはタイトルの通り Promise について調べていくっ

f:id:yum_fishing:20201222174835p:plain

Promise って?

Promise は、Javascript における非同期処理の結果を表すオブジェクトです。呼び出し側はこの Promise を使用することで非同期処理をより完結に記述することができます(つまりコールバック地獄にならない)。Swift 標準では Promise のようなものは用意されていませんが、RxSwift における Observable だったり、SwiftTask における Task だったりが役割的には近い感じだと思います。

ちなみに、Swift の Async/Await に関する Proposal もあるみたいなので、もしかしたら Swift6 から標準で Promise ライクに非同期処理を書ける時がくるかもしれませんね👀

Concrete proposal for async semantics in Swift · GitHub

Promise の基本

Promise には下記の3つ状態があり、そのうちの下二つ(fulfilledrejected)を引数にとります。

  • pending
  • fulfilled
  • rejected

第一引数には成功時のコールバックを、第二引数には失敗時のコールバックをそれぞれ記述します。

new Promise(function(resolve, reject) {
    resolve('success');
});

new Promise(function(resolve, reject) {
    reject('failure');
});

呼び出し側ではそれぞれの通知を下記のように受け取ることができます。

function test() {
    promise_sample()
        .then((data) => { console.log(data); }, 
        (error) => { console.log(error); }
    );
}

失敗時のコールバックは catch で受け取ることもできます。

function test() {
    promise_sample()
        .then((data) => {
            console.log(data);
        })
        .catch((error) => {
            console.log(error);
        });
}

また、失敗時の処理の後に then をつけることで、成功時と失敗時の処理の後に処理を書くことができるようです。RxSwift における onCompleted() みたいな感じですね🧑‍🔧

function test() {
    promise_sample()
        .then((data) => {
            console.log(data);
        })
        .catch((error) => {
            console.log(error);
        })
        .then(() => {
            console.log('Finish');
        });
}

という感じで本日は以上になります。また、わからないことが出てきたら Javascript 関連で記事を書こうかと思います✍️

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com