iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
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!"をテキスト描画している。
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 {...}
}
ビルド構成ファイル(build.gradle)の要素に対応。
プロジェクト構造の例を図にしてみる。
.
`-- 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
ビルド対象のモジュールを設定。
include ‘:app’
ルート・プロジェクト・ディレクトリにある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}"
...
}
モジュール固有の定義。
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.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);
Xcode 12 の Multiplatform 雛形から生成されるプロジェクトの構成を調べて。
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ディレクトリには、プラットフォーム固有のファイルが置かれている。
Unity Plug-insは外部で作成したコードをUnityに組み込む仕組みで、Managed plug-insとNative plug-insの2種類ある。
大雑把に説明すると、Managed plug-insはC#のコードで、Native plug-insはiOSやAndroidなどのプラットフォーム固有のコードとなる。
Unityの特殊フォルダは以下のとおり。
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
}
}