くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

ラベル(Text)のカスタマイズを行う【iOS】

更新なかなかできず・・・・

定期的には更新したいと思っています

今回はiOSでのラベルテキストのカスタマイズについてです。

わかりやすいようにラベルで記載していきますが、例えば、ボタンのテキストなども同様にカスタマイズできますのでぜひ応用してみてください。

デフォルトのラベルだと下記のようになると思います。

これだと場合によっては要件を満たせずに困ることがあると思います。

今回はそんな時のためにあるAttributedに関する説明です。

ラベルのInspectorを見てみるとTextがPlainになっていると思います。

f:id:kumaskun:20210704201228p:plain

Plainだとラベルのテキストのサイズやフォント色などは指定できますが、できることが限られます。

そこでAttributedに設定を変更してみると設定画面が少し変わると思います。

f:id:kumaskun:20210704201324p:plain

そこでどんなことができるのか少しみてみましょう!

部分的に文字の色を変える

Attributedに設定を変更すると部分的に色を変更することができます。

表示するテキストで色を変えたい部分を選択した状態で赤枠の色を変更する箇所で色を変更することができます。

f:id:kumaskun:20210704202030p:plain

変更するとこんな感じです。

 同様に選択した状態で右クリックするとフォントを変えたりできます。

f:id:kumaskun:20210704203541p:plain

行間を設定する

設定がPlainだと行間がありません。

行間を設定しないと2行分作るときにラベルを2つ設定しないといけなくて大変です。

どうやるかというと、下記の右のボタンを押すと詳細の設定ができる欄が表示されます。その中でSpacingで行間を設定できます。

f:id:kumaskun:20210704204425p:plain

Spacingで10を設定すると下記のようになります。

その他色々設定できるので、ぜひ試してみてください。

今回は個人的によく使うものを記載しましたが、その他様々なことができますので、試してみてください。

また、Attributedソースコードでも実現できるので、必要があれば確認してみてください。

iOSアプリでライセンス表示機能を作る

今回はライセンス機能についてです。

OSSのライブラリをiOSのアプリで利用している場合、使用したライブラリのライセンスをしかるべき場所に記載する必要があります。

ライブラリを追加する度にそれらを手動で更新すると手間になるので、それらをCocoaPodsの仕組みを使いながら自動で更新するようにしたいと思います。

Settings.bundleの作成

Settings.bundleという仕組みを使うことで、簡単にアプリの設定画面を実装することができます。

今回はSettings.bundleを使ってアプリの設定画面を実装し、その中にAcknowledgement(謝辞、ライセンス情報)を表示するようにしたいと思います。

さっそくSettings.bundleを作ってみます。Projectフォルダを右クリックし、 New FileのオプションからSettings Bundleを選択し、ファイルを作成してください。

f:id:kumaskun:20210425163754p:plain

Settings.bundleができたら、その中にあるRoot.plistを書き換えます。

Root.plistはアプリの設定に表示させる項目を設定するファイルです。

Root.plistを右クリックし、Open as > Source Code を選択してください。

下記は一例ですが、下記のように修正してください。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>StringsTable</key>
    <string>Root</string>
    <key>PreferenceSpecifiers</key>
    <array>
        <dict>
            <key>Type</key>
            <string>PSGroupSpecifier</string>
            <key>Title</key>
            <string>Group</string>
        </dict>
        <dict>
             <key>Title</key>
             <string>About</string>
             <key>Type</key>
             <string>PSGroupSpecifier</string>
             <key>FooterText</key>
             <string>Copyright © 2021 sample All Rights Reserved.</string>
         </dict>
         <dict>
             <key>Type</key>
             <string>PSTitleValueSpecifier</string>
             <key>DefaultValue</key>
             <string>1.0.0</string>
             <key>Title</key>
             <string>Version</string>
             <key>Key</key>
             <string>sbVersion</string>
         </dict>
         <dict>
             <key>Type</key>
             <string>PSChildPaneSpecifier</string>
             <key>Title</key>
             <string>Acknowledgements</string>
             <key>File</key>
             <string>Acknowledgements</string>
         </dict>
    </array>
</dict>
</plist>

FooterTextはその名の通り、Footerに表示させる機能で、Versionはバージョンを表示させる機能、Acknowledgementsは謝辞を表示させるためにあります。

続いて、ライセンス用のplistファイルを作成します。

Settings.bundle配下にAcknowledgement.plistを作成します。

Projectのフォルダーを右クリックしNew FileからProperty Listを作成してください。名前はAcknowledgements.plistで いいと思います。

f:id:kumaskun:20210425164639p:plain

作成したら、そのファイルをSettings.bundleの配下に移動させておきましょう。

CocoaPodsによるAcknowledgementsの書き出し

次に、CocoaPodsからOSSのライセンスの情報を直接書き出します。

PodFileに下記設定を追加しましょう。

post_install do | installer |
  require 'fileutils'
  FileUtils.cp_r('Pods/Target Support Files/Pods-(プロジェクト名)/Pods-(プロジェクト名)-acknowledgements.plist', '(Acknowledgements.plistのパス)', :remove_destination => true)
end

上記の設定を追加することでpod installやpod updateでライブラリの更新があった場合に変更が反映されます。

pod installやpod updateを行うことで、Acknowledgements.plistにライセンス情報が格納されます。

上記の設定が終われば、アプリをビルドするとアプリの設定画面から今回の設定した項目が確認できます。

アプリの設定に表示させたくない場合には該当のタグの中身を削除しましょう

アプリの中でライセンスを表示させる場合にはAcknowledgements.plistからライセンスの情報を取得すれば可能です。

Acknowledgements.plistは下記のように追加されているはずです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>PreferenceSpecifiers</key>
        <array>
            <dict>
                <key>FooterText</key>
                <string>○○○○○○○○○○○○○○○○○○○○○○</string>
                <key>License</key>
                <string>MIT</string>
                <key>Title</key>
                <string>○○○○○○</string>
                <key>Type</key>
                <string>PSGroupSpecifier</string>
            </dict>
        </array>
    </dict>
</plist>

FooterTextがライセンス表記、Licenseがライセンス種類、Titleがライブラリ名となります。

info.plistから情報を取得するのと同じようにタグの情報を取得して画面に表示させることが可能です。

if let path = Bundl.url(foeResource: "Acknowledgements", withExtension: "plist", subdirectory: nil, in: Bundle.main.url(forResource: "Settings", withExtensions: "bundle")!) {

    var items: [Any] = []
    
    let acknowledgements = NSDictionary(contentsOf: path)!
    items = acknowledgements["PreferenceSpecifiers"] as! [Any]

    items.removeFirst()
    items.removeLast()
}

removeFirst()とremoveLast()をしているのは最初と最後には関係のない情報が入っているためです。

最後に

ここまでライセンス表記の方法について説明しましたが、LicensePlistというライブラリで実現する方が便利かもしれません。

github.com

ライブラリをforkして使う

今回はライブラリをforkして使う方法です。

あまり使うことはないと思いますが、使いたいライブラリが更新されずに機能を追加したいけどできない場合に一部カスタマイズして使うために使ったりします。

まずは使いたいライブラリを下記をタップして、forkします。

forkすると自分のGithubに取り込まれるはずです。

f:id:kumaskun:20210413104843p:plain

次にPodFileで下記のように記載します。

pod 'ライブラリ名', :git => 'リポジトリのURL', :branch => 'ブランチ(masterなど)'

リポジトリのURLは下記のようにCodeを押すと確認できます。

f:id:kumaskun:20210413104853p:plain

この状態でpod install するとforkしたライブラリが取り込まれるのでカスタマイズしましょう。

そもそもforkせずにpod installして修正すればいいじゃんと思う方もいると思うのですが、そうするとライブラリを更新した際に修正が消えてしまうので、ライブラリをforkして使う方がいい方法と言われています。

iOSアプリのビルドのターゲット分けを行う

今回はiOSアプリのビルドのターゲット分けについて記載しようと思います。

例えば、開発環境と本番環境で処置を分けたい場合やAPIのURLを分けたい場合があると思います。

そんなときに1つのプロジェクトで複数の環境を管理するためにターゲット分けを行います。

ターゲットの作成

まずはターゲットの作成を行います。

下記のように既存のターゲットを右クリックしてDuplicateを選択してターゲットを複製します。

f:id:kumaskun:20210328213103p:plain

ターゲットを複製するとinfo.plistファイルも複製されるので、必要に応じて削除しましょう。(1つのファイルで管理する方が楽だと思います)

設定の追加

次に、Other Swift Flags-D TARGET名を設定します。

この値はプログラム内でTARGET毎に処理を切り替えたい場合に、どのTARGETでビルドされているかを判定するために使います。

Build SettingのOther Swift FlagsにTARGET名を定義します。

f:id:kumaskun:20210405231207p:plain

定義したTARGET名で下記のように条件分岐を行うことができます。

#if DEVELOP
//
#elseif STAGING
//
#endif

Schemeの設定

各TARGETが設定できたら、起動のためのSchemeを作成します。

Schemeを作成することでターゲットごとにビルドすることができます。

まず、Xcode左上のScheme名のところからManage Schemesを選択して、Schemeの一覧を表示します。

f:id:kumaskun:20210405232003p:plain

表示したときに名前が複製したときのものになっている可能性があるので、下記の赤枠で追加・削除することができるのでターゲットを修正しましょう。

f:id:kumaskun:20210405232230p:plain

f:id:kumaskun:20210405232322p:plain

補足

今回のように手間をかけなくてもプロジェクトを新規作成した場合にはDebugReleaseの2種類のターゲットが作られます。

f:id:kumaskun:20210405232621p:plain

なので、下記のようにソースコードで分岐は可能です。

#if Debug
//
#elseif Release
//
#endif

ただ、下記のような場合はターゲット分けの方がやりやすいので、個人的にはおすすめです。

・アプリに含めるリソースを変えたい

・Bundle IdentifierをTARGET毎に変えたい

・ホーム画面に表示されるアプリ名をTARGETによって変えたい

プッシュ通知の許可状況を確認する

今回は小ネタです

プッシュ通知を許可しているかどうか知りたいときありませんか?

そんなときに許可状況を確認するための実装です

 if UIApplication.shared.isRegisteredForRemoteNotifications {
      print("許可している")
} else {
      print("許可していない")
 }

本当に小ネタですが、勉強になったので、ブログにしました!

apkファイルをインストールする

今回はapkファイルをインストールする方法についてです

たまに開発中のものをapkファイルに渡すということありませんか?

そして、渡されたものをインストールして動きを確認することはありませんか?

今回はそんなときにインストールをする方法です

大前提として、端末の「提供元不明のアプリ」を許可してください

adbコマンドを使う

AndroidStudioを使っていることが前提ですが、adbコマンドを使ってapkファイルをインストールすることができます

adbコマンドを使うためには以下の設定が必要です

vi ~/.bash_profile

// Android SDKのパスを記入します
export PATH=$PATH:/Users/ユーザ名/Library/Android/sdk/platform-tools

※SDKのパスはAndroid Studioで「File」>「Other Settings」>「Default Project Structure…」に記載されています

// bash_profileを再度読み込む
source ~/.bash_profile

adbコマンドを使えるようになったら、adbコマンドでapkファイルをインストールします

adb install xxx.apk

xxx.apkはapkファイルを配置しているパスまで移動するかフルパスを指定します

サービスを経由してapkファイルを取り込む

AndroidStudioを持ってなくても端末にapkファイルを取り込むことができます

外部サービスを使えばいいのです

例えば、GmailやSlackやGoogle Driveなどです

ただし、容量の関係で取り込めない可能性があるので、注意です

プッシュ通知を処理する

今回はプッシュ通知についてです

プッシュ通知はローカル通知とリモート通知がありますが、今回はリモート通知についてです

簡単にローカル通知とリモート通知の違いを説明すると、ローカル通知はアプリ単体でアプリがフォアグラウンド以外なら通知が表示され、リモート通知は外部のサーバーから端末に対して通知したい場合に使います。主にWebサービスと連携するアプリで使用します

ローカル通知はアプリ単体で通知を表示できるので、実装すれば終わりですが、リモート通知の場合には外部のサーバーから端末に対して通知を行うので、外部サービスとの連携を行う必要があります。

そんなときに便利でよく使うのが、Firebase Cloud Messagingです

Firebase Cloud Messagingとは?

Firebase Cloud Messaging(FCM)について言及する前にiOSのプッシュ通知の仕組みについて説明します

iOSでは、APNsというプッシュ通知のサービスを使い、プッシュ通知を実現します

ドキュメントによると、通知をアプリに送信するサーバーであるプロバイダーからAPNsに送信のリクエストを投げて、アプリに通知を送信するという流れになります

APNSを簡単に補足すると、Push技術を使って常にオープンなIP接続を通してサードパーティ製アプリケーションのサーバーからの通知をアップルの端末に転送します

つまり、iOSアプリでプッシュ通知を実現するためにはAPNsに送信のリクエストを投げるというのが必要になります

では、FCMは何のために必要でしょうか?

FCMはAPNsにリクエストを投げるプロバイダーの役割を果たしてくれます

プロバイダーの役割はサーバーとほとんど同じ意味だと思うので、自前で作ることもできますし、Saasを使うこともできます

その中で、Saasのプロバイダーとして、提供されているのがFCMなんです

FCMは、サーバサイド、クライアントサイド(ここではアプリ)、ともに実装が必要なものの、APIが提供されており、柔軟なプッシュ通知送受信が実現可能なためよく使われています

※間違っていたら指摘してもらえるとありがたいです

FCMを使うために

FCMを使う準備は大まかには2つあります

APNsを使えるようにするための準備とFCMを使うための準備です

APNsを使えるようにするための準備はプッシュ通知用の証明書を作成して、登録することでFCMを使うための準備はFCMのコンソールに対象のアプリの情報を登録するなどです

ここでは詳細な手順は省きますが、自分が参考になった記事を最後に載せますで、ぜひ確認してみてください!!

プッシュ通知を送る処理、受け取る処理

ここからは、実際にプッシュ通知を受け取るための処理と受け取った後の処理について説明していきます

まずは、プッシュ通知を受け取るための処理です

プッシュ通知の許諾ダイアログを表示させる

まずは、アプリを最初に起動したときに表示されるプッシュ通知の許諾ダイアログを表示させるための処理です

import UIKit
import UserNotifications

class AppDelegate: UIResponder, UIApplicationDelegate {

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

        UNUserNotificationCenter.current().delegate = self
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: { _, _ in
        })

        application.registerForRemoteNotifications()

        return true
    }
}

これでプッシュ通知を許可するかどうかのダイアログを表示させます

FCMの処理を追加する

まずはPodfileに下記を追加してインストールしましょう

pod `Firebase/Core`
pod `Firebase/Messaging`

次にFirebaseの初期化とFCMのdelegateを使うための処理を記載します

didFinishLaunchingWithOptions launchOptionsで行います

import UIKit
import UserNotifications
import Firebase
import FirebaseMessaging

class AppDelegate: UIResponder, UIApplicationDelegate {

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

        FirebaseApp.configure()

        Messaging.messaging().delegate = self
    }
}

FCMのdelegateメソッドは下記になります

extension AppDelegate: MessagingDelegate, UNUserNotificationDelegate {

    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { 

        // トークンをアプリケーション サーバーに送信する処理
    }
}

上記の処理は登録トークンを取得するための処理です

このメソッドは通常、登録トークンを使用してアプリが起動するごとに 1 回ずつ呼び出されます

そのトークンを使ってどのデバイスにプッシュ通知を送るのかを判別するために使います

FCMの公式ドキュメントによると、下記のように記載があります

FCM SDK は、FCM登録トークンに対する APNs トークンのマッピングと、ダウンストリーム メッセージのコールバック処理中のアナリティクス データの取得という 2 つの主要領域で、メソッドの実装入れ替えを行います

つまり、FCMを使う場合にはFCMトークンを使い、FCMを使わない場合には別のトークンを使うことになります

トークンをアプリケーション サーバーに送信する処理はAPIで行う場合が多いと思うので、今回は割愛します

プッシュ通知を受け取った後の処理

プッシュ通知を受け取ったら、下記の処理が実行されます

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHanfler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { 
    }
}

上記のuserInfoペイロードの値が入っているはずなので、その後の処理を行ってください

プッシュ通知の準備段階での必要な手順の参考記事

【Swift5】リモートプッシュ通知の実装方法

↑だけでも十分準備ができるくらいいい記事です

バックグラウンド iOS アプリにテスト メッセージを送信する

Firebaseは公式ドキュメントが理解しやすいです

プッシュ通知の確認は下記のライブラリを使えば簡易的に確認できます

github.com

UITabBarControllerについて

iOSの開発では必須といっていいくらいUITabBarControllerは使うと思います

今回はそんなUITabBarControllerについて記載しようと思います

UITabBarControllerはstoryboardで作成する方法とコードで実装する方法があるので両方説明しようと思います。(個人的にはコード派です)

storyboardでの作成

まずはstoryboardで作成する方法です。

プロジェクトを作成したら、ViewControllerを選択した状態で、EditorでEmbed In → TabBarControllerを選択します

f:id:kumaskun:20210211213918p:plain

TabBarControllerを選択したら下記のようにTabBarが追加されていると思います

f:id:kumaskun:20210211214045p:plain

この状態だとTabBarのメニューは1つなので、メニューを追加します

ちなみにTabBarの表示できる個数は5つまでで、Human Interface Guidelinesによると、3〜5個が理想のようです。

新しくViewControllerを追加して、最初に作成したViewControllerを選択した状態で新しく作成したViewControllerへ伸ばすといくつかの選択肢が現れます

そこからview controllersを選択します

f:id:kumaskun:20210211214753p:plain

すると、下記のようにメニューが2つになります

f:id:kumaskun:20210211214951p:plain

メニューの名前やイメージ画像などはそれぞれのメニュー画面から変更でき、TabBarの設定変更(背景色変更など)はTabBarControllerからできます

f:id:kumaskun:20210211215539p:plain

コードでの実装

UITabBarControllerの画面を作成している場合には実装で作成するクラスと紐付ける必要があります(画面自体を作成しない場合には必要ないです)

f:id:kumaskun:20210211215923p:plain

まずは、メインとなるUITabBarControllerを継承したクラスを作成します

import UIKit

class MainTabBarController: UITabBarController {
    
}

次に表示させたいメニューのクラスを作成します

表示させたいメニューのクラスはUIViewControllerを継承します

import UIKit

class FirstMenuViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }
}
import UIKit

class SecondMenuViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

そして、UITabBarControllerのクラスファイルにタブとして表示させたいViewControllerを定義します

class MainTabBarController: UITabBarController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var menuViewControllers: [UIViewController] = []
        
        let firstMenuViewController = FirstMenuViewController()
        let secondMenuViewController = SecondMenuViewController()
        
        menuViewControllers.append(firstMenuViewController)
        menuViewControllers.append(secondMenuViewController)
        
        self.setViewControllers(menuViewControllers, animated: false)
    }
}

self.setViewControllers(viewControllers, animated: false)viewControllers = menuViewControllersでも可能です

UITabBarControllerはviewControllersというプロパティを持っているためそこにメニュー群の情報(ここでのmenuViewControllers)を代入してあげればいいです

もう一歩

UITabBarControllerを継承したクラスはアプリを起動したときに最初に表示される基底となる場合が多いと思います

なので、AppdelegateのdidfinishLaunchingWithOptionsなどで下記のように定義することが多いと思います

let controller = MainTabBarController()

window?.setRootViewControllerAnimated(controller, completionHandler: {
})

storyboardを使った方法だと画面遷移がわかりやすい反面、UITabBarControllerの画面を作らないといけないのが手間な気がします

UITabBarControllerはソースコードで作成して、メニューのControllerをstoryboardで作成するのが個人的にはおすすめです!

また、個人的にはまったのがUITabBarControllerのライフサイクルです

TabBarのアイテムが2つある画面を想定して説明します

・初回起動(1つめの画面)
1つめのitem: viewDidLoad()
1つめのitem: viewWillAppear()
1つめのitem: viewDidAppear()

・TabBar : 2つ目のボタンタップ
2つ目のitem: viewDidLoad()
2つ目のitem: viewWillAppear()
1つめのitem: viewWillDisappear()
1つめのitem: viewDidDisappear()
2つ目のitem: viewDidAppear()

・TabBar : 1つ目のボタンタップ
1つめのitem: viewWillAppear()
2つ目のitem: viewWillDisappear()
2つ目のitem: viewDidDisappear()
1つめのitem: viewDidAppear()

上記からわかるように、TabBarのアイテムは初期表示されれば、再度表示してもviewDidLoad()は行われないので、注意が必要です!

参照

UITabBarControllerの使い方