トップ 追記

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|

2021-04-05 [iOS]ユーザのトラッキングについて

iOS 14.5とiPadOS 14.5、tvOS 14.5で予告通り、IDFAを取得する場合はAppTrackingTransparencyのフレームワークを利用したユーザへの許諾が必要なる。

この件の告知文章をよく読むと、その他のいかなるトラッキングの形態(名前やメールアドレスによるトラッキングなど)についても、 App Store の「プライバシー情報」セクションで開示する必要があると説明されているので、この要件を満たさないと審査でリジェクトされる可能性があるということになる。


2021-03-31 [iOS]APNs 関連の更新

Apple Push Notificationサービス(APNs)関連で2つの大きな更新がある。

APNs Provider APIはバイナリインターフェイスとHTTP/2ベースの2種類がある。また、バイナリインタフェースも新旧の2形式がある。

2021年3月31日で、バイナリインターフェイスはサポート終了となる。

また、HTTP/2ベースの方も、GeoTrust Global CAルート証明書から新しいルート証明書(AAACertificateServices)への切り替えに伴いサーバ証明書が更新され、古いサーバ証明書は2021年3月29日で終了となるので、それまでに切り替えが必要となる。


2021-02-11 [Android][NDK]CMakeを設定する

ビルド周りを新しくしていったらndk-buildで問題が発生したため、CMakeについて調べたことを備忘録としてまとめる。

ndk-buildはGNU Makeベースのビルドの仕組みでAndroid独自のものとなり、Android.mkとApplication.mkに設定を記述する。

CMakeはAndroidのNDKビルド以外でも使用されている仕組みで、CMakeLists.txtに設定を記述する。

以下は簡単の共有ライブラリの記述の例。

cmake_minimum_required(VERSION 3.4.1)
 
add_library(
        # ライブラリ名
        native-lib
        
        # 共有or静的の指定
        SHARED
        
        # ソースファイル
        src/main/cpp/native-lib.cpp )
 
# ヘッダー検索パスの設定
include_directories(src/main/cpp/include/)

# 変数log-libにパスlogを代入
find_library(
        log-lib
        log )
 
# 静的ライブラリを追加
add_library( app-glue
        STATIC
        ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
 
# ライブラリをリンク
target_link_libraries(
        native-lib
        app-glue
        ${log-lib} )

ビルド済みライブラリを追加する場合はIMPORTフラグを使用し、set_target_propertiesでパスを指定する。

add_library( imported-lib
        SHARED
        IMPORTED )
 
set_target_properties(
        imported-lib
        PROPERTIES IMPORTED_LOCATION
        imported-lib/src/${ANDROID_ABI}/libimported-lib.so )
 
# ヘッダー検索パスに追加
include_directories( imported-lib/include/ )

ビルド済みライブラリも同様にリンクを設定する。

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

CMakeプロジェクトは階層化できるので、モジュール毎に用意して、大元で束ねられる。

# lib_src_DIRに対象となるCMakeプロジェクトのパスを設定
set( lib_src_DIR ../gmath )
 
# lib_build_DIRに出力先のパスを設定し、そのディレクトリを作成する。
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})
 
# 下位のCMakeLists.txtの場所と出力先を追加。
# as a build dependency.
add_subdirectory(
        ${lib_src_DIR}
        ${lib_build_DIR} )
 
# 下位の階層のライブラリを追加
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                       ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )
 
# ライブラリをリンク
target_link_libraries( native-lib ... lib_gmath )

2021-01-19 [Unity][iOS][Android]Unity Native Plug-insについて

Unity Plug-insは外部で作成したコードをUnityに組み込む仕組みで、Managed plug-insとNative plug-insの2種類ある。

大雑把に説明すると、Managed plug-insはC#のコードで、Native plug-insはiOSやAndroidなどのプラットフォーム固有のコードとなる。

Unityの特殊フォルダは以下のとおり。

Assets
アセットが収められるフォルダ。
Editor
ランタイムでなくエディタ用のスクリプトを格納するフォルダ。
Editor default resources
エディタ用リソースのフォルダ。
Gizmos
見えないデザインの詳細を可視化する際に利用するアイコンのフォルダ。
Plugins
プラグインを格納するフォルダ。
Resources
スクリプトで読み込むリソースのフォルダ。
Standard Assets
インポートした標準のアセットパッケージのフォルダ。
StreamingAssets
このフォルダに格納されたファイルはターゲットにコピーされ利用できる。

Native plug-insのネイティブ・ソースを決められた形式のフォルダに格納すると、自動的に統合される。

iOSの場合は、Assets/Plugins/iOS に、ファイル名のsuffix が.a、.m、.mm、.c、.cpp のものが対象となる。

例えば、Assets/Plugins/iOS/Utils.c というファイルを配置する。

float FooPluginFunction()
{
    return 5.0F;
}

このコードを呼び出すC#スクリプトは以下の通り。

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
 
public class Utils : MonoBehaviour
{
    #if UNITY_IPHONE
    [DllImport ("__Internal")]
    private static extern float FooPluginFunction ();
    #endif
 
    void Awake () {
        #if UNITY_IPHONE
        print (FooPluginFunction ());
        #endif
    }
}

このコードはiOSフレームワークを追加で必要としていないので、iOSビルドすれば動作する。

Androidでは、JavaやKotlinのソースファイルをプラグインに追加できて、InspectorウィンドウのSelect platforms for plubinでAndroidのみ選択されている状態にすればいい。

例えば、Assets/Plugins/Android/Utils.java というファイルを配置する。

package com.example;
 
class Utils {
    public static double fooPluginFunction() {
        return 5.0;
    }
}

このコードを呼び出すC#スクリプトは以下の通り。

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
 
public class Utils : MonoBehaviour
{
    void Awake () {
        #if UNITY_ANDROID
        using (AndroidJavaClass cls = new AndroidJavaClass("com.example.Utils")) {
            Debug.Log("FooPluginFunction: " + cls.CallStatic("fooPluginFunction"));
        }
        #endif
    }
}

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/unity/AddOn - GitHub

2021-01-12 [Cocoa][Swift]Multiplatform

Xcode 12 の Multiplatform 雛形から生成されるプロジェクトの構成を調べて。

Xcodeで表示される内容は以下の通り。

Xcode

ディレクトリ/ファイル構成は以下の通り。

.
`-- Bedrock
    |-- Bedrock.xcodeproj
    |-- iOS
    |   `-- Info.plist
    |-- macOS
    |   |-- Info.plist
    |   `-- macOS.entitlements
    |-- Shared
    |   |-- Assets.xcassets
    |   |-- BedrockApp.swift
    |   `-- ContentView.swift
    |-- Tests iOS
    |   |-- Info.plist
    |   `-- Tests_iOS.swift
    `-- Tests macOS
        |-- Info.plist
        `-- Tests_macOS.swift

SwiftUIでmacOSとiOSのソースコードは共通化できるので、Sharedディレクトリにソースファイルは置かれ、macOS/iOSディレクトリには、プラットフォーム固有のファイルが置かれている。

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/multiplatform/Bedrock - GitHub

2021-01-06 [Android]Gradleについての備忘録

_ ビルド要素

ビルド構成ファイル(build.gradle)の要素に対応。

  • ビルドタイプ(debug, release, etc)
    パッケージ化で使用するプロパティを定義。
  • プロダクト・フレーバー(例: 無料版, 有料版)
    異なる種類の定義。
  • ビルド・バリアント
    ビルドタイプとプロダクト・フレーバーを組み合わせたもの。
  • マニフェスト・エントリ
    ビルド・バリアント毎にマニフェスト・ファイルの一部のプロパティ値を設定。
  • 署名
    ビルドの種類毎に署名の内容を設定。
  • コードとリソースの圧縮
    ビルド・バリアント毎にProGuard ルールファイルを指定。
  • 複数 APK サポート
    ビルドの種類毎のコードとリソースを設定。

_ プロジェクト構造

プロジェクト構造の例を図にしてみる。

.
`-- MyApp                           Project(ルート・プロジェクト・ディレクトリ)
    |-- gradle.properties           Gradle プロパティ・ファイル
    |-- local.properties            Gradle プロパティ・ファイル
    |-- build.gradle                トップレベル・ビルド構成ファイル
    |-- settings.gradle             Gradle設定ファイル
    `-- app                         Module
        |-- build.gradle            モジュール・レベル・ビルド構成ファイル
        |-- build
        |-- libs
        `-- src
            |-- main                Sourceset
            |   |-- java
            |   |   `-- com.example.myapp
            |   |-- res
            |   |   |-- drawable
            |   |   |-- layout
            |   |   `-- ...
            |   `-- AndroidManifest.xml
            |-- buildType           特定のビルドタイプのSourceset
            |-- productFlavor       特定のプロダクト・フレーバーのSourceset
            `-- productFlavorBuildType          特定のビルド・バリアントのSourceset

_ Gradle 設定ファイル(settings.gradle)

ビルド対象のモジュールを設定。

include ‘:app’

_ トップレベル・ビルド構成ファイル(build.gradle)

ルート・プロジェクト・ディレクトリにあるbuild.gradle。buildscriptブロックにモジュール共通のGradleリポジトリと依存関係(Android Plugin for Gradle など)を定義。

buildscript {
    repositories {
        google()
        jcenter()
    }
 
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.0'
    }
}
 
allprojects {
    repositories {
        google()
        jcenter()
    }
}
 
ext {
    compileSdkVersion = 28
    supportLibVersion = "28.0.0"
}

extブロックにモジュール共通のプロパティを定義。このプロパティは、モジュール・レベル・ビルド構成ファイルから利用できる。

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    ...
}
...
dependencies {
    implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
    ...
}

_ モジュール・レベル・ビルド構成ファイル(build.gradle)

モジュール固有の定義。

apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 28
    buildToolsVersion "29.0.2"
 
    defaultConfig {
        applicationId 'com.example.myapp'
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }
 
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
 
    flavorDimensions "tier"
    productFlavors {
        free {
            dimension "tier"
            applicationId 'com.example.myapp.free'
        }
 
        paid {
            dimension "tier"
            applicationId 'com.example.myapp.paid'
        }
    }
 
    splits {
        density {
            enable false
            exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
        }
    }
}
 
dependencies {
    implementation project(":lib")
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

デフォルトのソースセットの設定は変更できる。

android {
    ...
    sourceSets {
        main {
            // デフォルトは'src/main/java'
            java.srcDirs = ['other/java']
 
            res.srcDirs = ['other/res1', 'other/res2']
 
            manifest.srcFile 'other/AndroidManifest.xml'
            ...
        }
 
        androidTest {
            setRoot 'src/tests'
            ...
        }
    }
}
...

_ Gradle プロパティ ファイル

gradle.propertiesにはGradle設定をlocal.propertiesにはローカル環境プロパティ(ndk.dirやsdk.dirなど)を定義する。

モジュールのbuild.gradleで署名が設定できる。

android {
    ...
    defaultConfig { ... }
 
    signingConfigs {
        release {
            storeFile file("my-release-key.jks")
            storePassword "password"
            keyAlias "my-alias"
            keyPassword "password"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            ...
        }
    }
}
...

build.gradleに署名情報を記述したくない場合は、ルート・プロジェクト・ディレクトリにkeystore.propertiesを置く。

storePassword=myStorePassword
keyPassword=myKeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation

モジュールのbuild.gradleのandroidブロックの前にkeystore.propertiesを読み込む定義を記述する。

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
 
android {
    signingConfigs {
        config {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }
    ...
}
...

ビルドするとBuildConfigクラスが生成されるが、カスタムフィールドを追加することができるので、これを使ってアプリのコードに値を渡せる。

android {
    ...
    buildTypes {
        release {
            buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
            resValue("string", "build_time", "${minutesSinceEpoch}")
            ...
        }
        debug {
            buildConfigField("String", "BUILD_TIME", "\"0\"")
            resValue("string", "build_time", "0")
        }
    }
}
...

アプリのコードでは、以下のように参照する。

...
Log.i(TAG, BuildConfig.BUILD_TIME);
Log.i(TAG, getString(R.string.build_time));

マニフェストにも値を渡せる。

android {
    defaultConfig {
        def filesAuthorityValue = applicationId + ".files"
        manifestPlaceholders =
            [filesAuthority: filesAuthorityValue]
        buildConfigField("String",
                         "FILES_AUTHORITY",
                         "\"${filesAuthorityValue}\"")
  }
  ...
}
...

マニフェストでは、以下のように参照する。

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${filesAuthority}"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
    </application>
</manifest>

アプリのコードからの参照は以下の通り。

...
Uri contentUri = FileProvider.getUriForFile(getContext(),
    BuildConfig.FILES_AUTHORITY,
    myFile);

2021-01-05 [Android][Gradle][ndk-build]既存のネイティブ・ソースをGradleに組み込む

ndk-buildを使っている既存のネイティブ・ソースをGradleに組み込み手順を調べた。

Android Studio UIを利用すると思わぬ副作用が発生する懸念があるのと、Android Studioの利用は次の段階で検討するので、手動で対応する方法のみとなっている。

gradle.propertiesにCMakeでなくndk-buildを選択していると設定する。

PROP_APP_ABI=armeabi-v7a:arm64-v8a
PROP_BUILD_TYPE=ndk-build

build.gradleのdefaultConfigブロックにndk-buildのオプションを設定する。

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      if (PROP_BUILD_TYPE == 'ndk-build') {
        ndkBuild {
          arguments '-j4'
          arguments 'NDK_MODULE_PATH=modules'
        }
      }
      else if (PROP_BUILD_TYPE == 'cmake') {
        cmake {
          arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
          cFlags "-D__STDC_FORMAT_MACROS"
          cppFlags "-fexceptions", "-frtti"
        }
      }
    }
  }
 
  buildTypes {...}
 
  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        if (PROP_BUILD_TYPE == 'ndk-build') {
          ndkBuild {
            ...
          }
        }
        else if (PROP_BUILD_TYPE == 'cmake') {
          cmake {
            ...
            targets "native-lib-demo",
                    "my-executible-demo"
          }
        }
      }
    }
 
    paid {
      ...
      externalNativeBuild {
        if (PROP_BUILD_TYPE == 'ndk-build') {
          ndkBuild {
            ...
          }
        }
        else if (PROP_BUILD_TYPE == 'cmake') {
          cmake {
            ...
            targets "native-lib-paid",
                    "my-executible-paid"
          }
        }
      }
    }
  }
 
  externalNativeBuild {
    if (PROP_BUILD_TYPE == 'ndk-build') {
      ndkBuild {...}
    }
    else if (PROP_BUILD_TYPE == 'cmake') {
      cmake {...}
    }
  }
}

buidl.gradleでndk.abiFiltersフラグにABIを設定する。

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      if (PROP_BUILD_TYPE == 'ndk-build') {
        ndkBuild {...}
      }
      else if (PROP_BUILD_TYPE == 'cmake') {
        cmake {...}
      }
    }
 
    ndk {
      abiFilters = []
      abiFilters.addAll(PROP_APP_ABI.split(':').collect{it as String})
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

2021-01-02 [SwiftUI]Appプロトコル

XcodeでiOSの新規Appプロジェクトを生成すると雛形から作られるのが、Appプロトコルを実装する〜Appクラスだ。

import SwiftUI
 
@main
struct LandmarksApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

計算型プロパティbodyの実装は必須となる。

WindowGroupはビュー階層のコンテナ。

ContentViewはアプリで独自に実装したビュー。

import SwiftUI
 
struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
            .padding()
    }
}
 
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Viewプロトコルを実装したContentViewがアプリ独自のビュー。PreviewProviderプロトコルを実装したContentView_PreviewsはXcodeのプレビュー表示にContentViewを表示させるためのもの。

ContentViewの計算型プロパティbodyにビューの内容を実装する。

サンプルコードでは、文言"Hello, World!"をテキスト描画している。


2020-12-23 [Cocoa][Swift]AirPlay 2 について

HomePod miniは2台でステレオ再生ができるということで購入したのだが、使ってみたところ、それはAirPlay 2を利用した場合だった。MacintoshにレコードプレーヤーをLINE入力で繋いだので、レコードがステレオで再生されることを夢見たのだが、サウンドの入力を出力に繋ぐために利用しているQuickTime PlayerはAirPlay 2に対応していないので、ステレオ再生できない。そもそも、QuickTime Playerを使う必要があるということも面倒なので、レコードプレーヤーの音声を再生するアプリを制作作するため、AirPlay 2 に対応する手順を調べた。

AVAudioSessionのroute-sharing policyに .longForm を設定する

let audioSession = AVAudioSession.sharedInstance()
try audioSession.setRouteSharingPolicy(.longForm)
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(AVAudioSessionCategoryPlayback,
                             mode: AVAudioSessionModeDefault,
                             routeSharingPolicy: .longForm )

AVRouteDetectorを使って出力先があるかどうか調べて、AVRoutePickerView で出力先を選択する。

MPRemoteCommandCenter を使って再生が制御できるようにする。再生中のメディアの情報は MPNowPlayingInfoCente から得る。

AVPlayer 又は AVQueuePlayer を利用するか、 AVSampleBufferAudioRenderer と AVSampleBufferRenderSynchronizer で再生を行う。

情報は少ないが、サンプルコードを見つけた。


2020-12-13 [Cocoa][ObjC]iOS 12以前のUISceneへの対処

確かXcode 11からだったと思うが、XcodeのiOS Appの雛形から生成されるプロジェクトでビルドしたアプリがiOS12以前だと正しく描画できない状態となる。

自分の記憶だとビルドエラーになったと思っていたのだが、Objective-Cだからか警告メッセージが表示されるだけで、実機での起動はできる。

原因は、同一アプリの画面を複数表示するために導入されたUISceneがiOS13以降でないと対応していないためだ。

以前のAppDelegateで行われていた画面周りのコードがSceneDelegateに移動し、iOS12以前だとAppDelegateに画面周りのコードが存在しないため、表示がおかしくなっている。

試行錯誤した結果、対処方法は簡単だった。SceneDelegateで定義されているwindowプロパティをAppDelegateでも定義するだけでOKだった。

@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow * window;
 
@end

トップ 追記