20201010のiOSに関する記事は4件です。

swiftで文字列を1文字ずつの配列に変換する

AtCoderの問題を解いている際に、文字列を1文字ずつ分割する必要があったのでメモ

    let charArray = Array("abcdef")

    print(charArray)
    // ["a", "b", "c", "d", "e", "f"]
    print(type(of: charArray[0]))
    // Character
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneで四隅のタッチダウン反応が遅い問題の解決

問題

添付スクショのように画面端に配置したボタンからUIControl.Event.touchDownイベントを受け取ろうとすると、タッチダウンしてワンテンポ遅れてイベントがきます。

解決策

OSのジェスチャイベントが先に優先されるため、アプリへのイベントが遅れるようです。
そこで、下記のようにpreferredScreenEdgesDeferringSystemGesturesをオーバーライドして、アプリ側のイベントを優先するように指定します。

class ViewController: UIViewController {
    override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
        // 右端のボタンダウン反応が改善できます
        [.right]
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

どこよりも丁寧なGoogle Maps SDK for iOSの使い方

「Mapを表示するぞ!!」

…と、何だかんだiOSアプリを本格的に作成していくためのスタートラインとして、まずはAPIの使い方を学習していきました。

Maps SDK for iOSのチュートリアルは多く存在しますが、右も左も分からない初心者にはあまり丁寧な記事が見つからなかったので、作成がてらアウトプットも兼ねて順を追って紹介していきます。

※開発環境
Xcode 12
Swift 5.3

APIって?

APIとはApplication Programming Interface(アプリケーション・プログラミング・インターフェイス)略称です。

APIについて

APIとは、あるコンピュータプログラム(ソフトウェア)の機能や管理するデータなどを、外部の他のプログラムから呼び出して利用するための手順やデータ形式などを定めた規約のこと。

(なるほど、分からん…!)

概要を端的にすると、ソフトウェア同士のやりとりの橋渡しの機能がAPIとなります。
GoogleやTwitterなど、多くの会社はAPIを提供しています。その使いたい対象の機能(サービス)を他のプログラムから呼び出してもらうための命令や関数のことをAPIといいます。

Google Mapを使えるように設定

GoogleのMap API(iOS)を使いたいので、設定を行います。

Google Maps Platformに飛び
画面右上のConsoleに移動します。

image_01.png

「プロジェクトの選択」をクリックし、新しいプロジェクトを作成します。

image_02.png

プロジェクト名を入力後、作成ボタンを押します。

image_03.png

プロジェクトが開くと思うので、APIからMaps SDK for iOSを選択

※ホームに遷移していた場合

image_04.png

APIとサービス → ダッシューボードを選択

image_05.png

APIとサービスの有効化をクリック

image_06.png
image_08_1.png

APIライブラリの画面が出てきますので、Maps SDK for iOSを選択してください。

image_07.png

有効にするをクリックします。

image_08.png

管理画面が出てきますので、認証情報からに「認証情報を作成」を押して「APIキー」を選択します。

image_09.png

これでAPIキーが作成されました。
しかし、まだ設定は終わっていません。キーに制限がないとの注意が出ますので、制限を有効していきます。対象となるAPIキーを選択して設定していきます。

image_10.png

APIキーの制限と名前変更から今回使用したいMaps SDK for iOSを選択チェックして保存します。

これでGoogle MapsのAPIを使用する設定が終わりました。

APIKeyを使う

Xcodeからプロジェクトを作成し、Podfileに以下を記載しpod installします。

Podfile
target 'プロジェクト名' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  source 'https://github.com/CocoaPods/Specs.git' #追加
  pod 'GoogleMaps' #追加
  pod 'GooglePlaces' #追加
  # Pods for プロジェクト名

end

次にAppDelegateにAPIKeyを設定していきます。
AppDelegate.swiftを開いて、import GoogleMapsを追加します。

※1didFinishLaunchingWithOptionsのメソッド部分に以下を追記します。
※1はアプリ起動後にAppDelegateのdidFinishLaunchingWithOptionsが呼ばれ、この部分で全体的な機能の初期化処理を行います。

AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        #発行したAPIキーを設定
        GMSServices.provideAPIKey("発行したAPIキーをここに記載")

        return true
    }

次にInfo.plistに移動し、Information Property ListにArrayにgooglechromescomgooglemapsを追加しましょう。
image_11.png

ViewController.swiftを開きimportを追加しましょう。

import GoogleMaps
import CoreLocation

viewDidLoadの中に以下のコードを書いていきます。

ViewController.swift
 override func viewDidLoad() {
        super.viewDidLoad()
        #表示するマップの座標位置と表示されるサイズを生成
        let camera = GMSCameraPosition.camera(withLatitude: 34.6862, 
                                                                                  longitude: 135.5196, zoom: 6.0)
        let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        mapView.isMyLocationEnabled = true #現在地情報を有効にする
        view = mapView #UIViewのインスタンス

        #指定箇所にピンを立てる
        let marker = GMSMarker()
        marker.position = CLLocationCoordinate2D(latitude: 34.6862,
                                                                                    longitude: 135.5196)#緯度、軽度を設定
        marker.title = "Osaka"
        marker.snippet = "Japan"
        marker.map = mapView
    }

起動

ビルド&ランをしましょう。
実行後、選択したロケーションにピンが立っているかと思います。

image_12.png

以上、Google Maps APIの使い方でした。

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

iOS 14 アプリケーションのWidgetの追加(通常のWidget、Intentsを使用した構成可能なWidget)

本記事では以下の内容を説明します。

  1. 既存のアプリケーションへのウィジェットの追加
  2. ⭐️ ユーザーがコンテンツを構成できる (都市の選択など) ウィジェットの追加

ezgif-6-bf9e4b4783c9.gif

完成したソースコードはこちらから確認できます。

既存のアプリケーションへのウィジェットの追加

アプリケーションターゲットの作成

既存の iOS アプリケーションへのウィジェットの追加は簡単です。ターゲット Widget Extension を追加してください。

image

さて、 Include Configuration Intent というボックスを選択していることを確認してください。この記事のパート2では、その構成ファイルが必要になるためです。

image

データ構造

これで WidgetExample-Widget という新しいフォルダーが表示されます。クリックしてファイル WidgetExample_Widget.swift を開き、そのファイルのコンテンツを削除して、このガイドに従います。

ウィジェットに表示したいデータのデータ構造を作成できます。この例では、猫の情報を表示します!

struct CatEntry: TimelineEntry {
    var date: Date

    var name: String
    var lastFed: Date
    var lastPlayedWith: Date
}

IntentTimelineProvider構造の作成

IntentTimelineProvider 構造は、次の3種類のコンテンツを提供します:
1> placeholder (プレースホルダー)は、ウィジェットがロードされているときに表示されます。
2> getSnapshot は、ウィジェットギャラリーに表示されます。
3> getTimeline は、実際のウィジェット表示に使用されます。

まず、タイプ IntentTimelineProvider に確認するプログラム構造体を作成し、次に Entry のタイプを定義します。

struct CatProviderStatic: TimelineProvider {
    typealias Entry = CatEntry

    func getSnapshot(in context: Context, completion: @escaping (CatEntry) -> Void) {
        //TODO
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
        //TODO
    }

    func placeholder(in context: Context) -> CatEntry {
        //TODO
    }

}

getSnapshot 機能については、あなたのウィジェットが何の情報を提供するかをユーザーが理解できるよう、ウィジェットビューに例の値を提供することができます:

func getSnapshot(in context: Context, completion: @escaping (CatEntry) -> Void) {
    let entry = CatEntry(date: Date(), name: "猫の名前", lastFed: Date(), lastPlayedWith: Date())
    completion(entry)
}

プレースホルダービューについては、以下のように空の値または例の値を表示できます:

func placeholder(in context: Context) -> CatEntry {
    return CatEntry(date: Date(), name: "猫の名前", lastFed: Date(), lastPlayedWith: Date())
}

そして、タイムライン表示については、表示する実際のコンテンツを提供できます。

この例では、ただ静的データ値を表示します。あなたのアプリケーションで、Core Dataそのデータを共有する方法についての記事はこちら)、またはオンラインから、CloudKit から、または UserDefaults からのコンテンツをフェッチできます。

func getTimeline(in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
    let entry = CatEntry(date: Date(), name: "ネコノヒー", lastFed: Date(), lastPlayedWith: Date())
    let timeline = Timeline(entries: [entry], policy: .atEnd)
    completion(timeline)
}

ウィジェットビューのデザイン

ウィジェットの SwiftUI ビューをデザインできるようになりました。

struct CatWidgetView: View {

    @Environment(\.widgetFamily) var family

    var entry: CatEntry

    var body: some View {

        VStack {

            if family == .systemMedium || family == .systemLarge {
                Image("kitty")
                    .resizable()
                    .frame(width: 50, height: 50)
                    .padding(.vertical, 5)
            }

            Text(entry.name)
                .font(.headline)
                .padding(1)

            Text("最後に遊んだ時間 " + entry.lastPlayedWith.getString())
                .font(.caption)
                .padding(.horizontal)

            Text("最後に餌をあげた時間 " + entry.lastFed.getString())
                .font(.caption)
                .padding(.horizontal)

        }

    }

}

ウィジェットのサイズを確認するには @Environment(\.widgetFamily) var family 変数を使用できます。

この例では、ウィジェットのサイズが画像の表示に十分な大きさである場合の猫の画像を表示しています。

if family == .systemMedium || family == .systemLarge {
    Image("kitty")
        .resizable()
        .frame(width: 50, height: 50)
        .padding(.vertical, 5)
}

ウィジェットアプリケーションをコーディングします

これで、ウィジェットアプリケーションをコーディングできるようになりました。

@main
struct CatWidget: Widget {

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: "CatWidget", intent: ConfigurationIntent.self, provider: CatProvider()) { entry in
            CatWidgetView(entry: entry)
        }.configurationDisplayName("猫")
        .description("いつ猫に餌をあげたり遊んだりしたか見てみましょう。")
    }

}

これで、シミュレーターでアプリを実行して、設計したばかりのウィジェットを画面に追加できるようになりました。

image

ユーザーがウィジェットを構成できるようにします

お天気ウィジェットを使ったことがあれば、ウィジェットを長押しして構成することにより、表示される都市をユーザーが選択できることをご存じでしょう。Intents フレームワークとTargetを使用して、この機能を追加することができます。

Intents プログラムターゲットの追加

image

この段階では UI 要素は不要なため、オプション Include UI Extension のチェックは外して結構です。

image

作成された Intents ターゲットページで、Supported Intents という名前のセクションを見つけます。ConfigurationIntent という名前のアイテムを新規作成します。名前は [インテント名]Intent とします。インテント名は左側の .intentdefinition ファイル内に表示されます。

スクリーンショット 0002-10-09 15.16.55.png

スクリーンショット 0002-10-09 15.11.01.png

.intentdefinition の構成

次に、以前に作成したウィジェット内の WidgetExample_Widget.intentdefinition ファイルにつき、新しく作成された Intents 拡張機能をインテントのターゲットとして追加します。

画面左側の Configuration をクリックします。

image

右側で、以下の画像と構成が同じであることを確認してください。

image

次に、cat という名の新しいパラメータを追加します。

image

新たに作成された cat パラメータの設定画面で、猫の識別子を入力として使用するため、タイプ TypeString を選択します。

image

IntentHandler の構成

次に、IntentHandler.swift ファイルを開きます。このファイルでは、ユーザーがウィジェットの構成を行うオプションを提供します。この例では、オプションは猫の識別子となります。

クラスの INExtension タイプの隣に ConfigurationIntentHandling キーワードを追加すると、Xcode ソフトウェアが自動で次に追加すべき関数の名前を表示してくれます。

class IntentHandler: INExtension, ConfigurationIntentHandling {
    ...
}

この例では、完成した IntentHandler.swift ファイルは以下のとおりとなります。

class IntentHandler: INExtension, ConfigurationIntentHandling {


    func provideCatOptionsCollection(for intent: ConfigurationIntent, searchTerm: String?, with completion: @escaping (INObjectCollection<NSString>?, Error?) -> Void) {
        let catIdentifiers: [NSString] = [
            "ネコノヒー",
            "ムギ",
            "アズキ"
        ]
        let allCatIdentifiers = INObjectCollection(items: catIdentifiers)
        completion(allCatIdentifiers, nil)
    }

    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.

        return self
    }

}

関数 provideCatOptionsCollection では、値のリストを入力する必要があります。これらの値は、実際には User DefaultsCore Data、オンラインのいずれかからフェッチできます。この例では値がハードコーディングされています。

「App Extensions」で「Core Data」を使用する

IntentTimelineProvider を作成

本記事のパート1では、TimelineProvider を使用しました。今回は、IntentTimelineProvider を使用します。

IntentTimelineProviderTimelineProvider の間のデータ構造はほぼ同一です。追加の typealias を宣言する必要がある点が違いとなります。

typealias Intent = ConfigurationIntent

もう1つの違いは、次のとおりです。各関数で、インテントの選択を表す追加のパラメーター ConfigurationIntent を取得します。

struct CatProvider: IntentTimelineProvider {

    typealias Intent = ConfigurationIntent
    typealias Entry = CatEntry

    func placeholder(in context: Context) -> CatEntry {
        let entry = CatEntry(date: Date(), name: "", lastFed: Date(), lastPlayedWith: Date())
        return entry
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (CatEntry) -> Void) {
        let entry = CatEntry(date: Date(), name: "猫の名前", lastFed: Date(), lastPlayedWith: Date())
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
        let entry = CatEntry(date: Date(), name: configuration.cat ?? "", lastFed: Date(), lastPlayedWith: Date())
        let timeline = Timeline(entries: [entry], policy: .atEnd)
        completion(timeline)
    }

}

configuration.cat プロパティを使用して、ユーザーが選択したオプションの値を読み取ることができます。

let entry = CatEntry(date: Date(), name: configuration.cat ?? "", lastFed: Date(), lastPlayedWith: Date())

CatWidget のコードを更新します。

パート1では StaticConfiguration を使用しましたが、このパートでは IntentConfigurationSupported Intents で設定した名前)を使用します。

@main
struct CatWidget: Widget {

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: "CatWidget", intent: ConfigurationIntent.self, provider: CatProvider()) { entry in
            CatWidgetView(entry: entry)
        }.configurationDisplayName("猫")
        .description("いつ猫に餌をあげたり遊んだりしたか見てみましょう。")
    }

}

シミュレーターでプログラムを実行できるようになりました。ウィジェットを長押しすると Edge Widget というオプションが表示され、猫の名前を変更できます。

ezgif-6-bf9e4b4783c9.gif

⭐️

「App Extensions」で「Core Data」を使用する

こちらのウェブページにアクセスすると、私の公開されているQiitaの記事のリストをカテゴリー別にご覧いただけます。

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