20200525のiOSに関する記事は5件です。

[Xcode]The maximum number of apps for free development profiles has been reachedと出てインストールできない。

原因

自作のアプリやプロビジョニングプロファイルを削除しても、インストールに失敗する場合は無料枠プロファイルのカウント処理がバグっている可能性が高いです。

対応

iPhoneをMacに繋いで、コンソールでログを表示しながら再度インストールを試してください。

  1. Window->Devices and Simulators->対象のデバイスを選択してOpen Consoleボタンをクリックでログを表示
  2. アプリを実行して対象デバイスへのインストールを試みてください。
  3. MIFreeProfileValidatedAppTrackerでログを絞り込んでください。
[MIFreeProfileValidatedAppTracker _onQueue_addReferenceForApplicationIdentifier:bundle:error:]: 182: This device has reached the maximum number of installed apps using a free developer profile: {(
    "<bundleid>",
    "<bundleid>",
    "<bundleid>"
)}

というようなエラーが出ているかと思います。

  1. その一覧に表示されているアプリを削除または、再インストール
  2. 自作アプリをインストール

の順番で実行してみてください。

情報源

https://stackoverflow.com/questions/61953293/the-maximum-number-of-apps-for-free-development-profiles-has-been-reached-xcode/61978102#61978102

3日ほど悩んだので取り急ぎ共有しました。

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

iOSアプリのローカライズとローカリゼーションのデバッグ - 開発言語の変更、ストーリーボードと文字列のローカライズ、ローカライズされていない文字列の特定とデバッグ

ローカリゼーションとは?

自分が作ったアプリが人気になり、英語など他の言語にそのアプリを翻訳してもっと多くの人が使えるようにしたいと思うときが来るかもしれません。iOSのシステムは、ユーザー設定に基づきコンテンツを適切な言語で表示するなど、大方のローカリゼーションを行ってくれます。

アプリをローカライズする方法

Development language の変更

開発言語とは、アプリの開発時に使用する言語のことを指します。まず、設定の内容を確認する必要があります:

プロジェクトファイルをクリックし、(ターゲットではなく)プロジェクトファイルを選択します。ローカリゼーションのセクションで、Development Language が日本語なのか英語なのかを確認します。日本語の場合、設定は正しいので、何もする必要はありません。英語の場合、引き続き開発言語の変更のガイドに従ってください。

スクリーンショット 2020-05-24 16.05.37.png

次に Xcode を閉じ、.xcodeproj ファイルを右クリックして選択し、このファイルの内容を表示する:

スクリーンショット 2020-05-24 16.08.33.png

project.pbxproj ファイルを見つけ、テキストエディターで開く。

スクリーンショット 2020-05-24 16.09.42.png

developmentRegion を検索し、ja に変更する。
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;

上記のコードを以下に変更する

compatibilityVersion = "Xcode 9.3";
developmentRegion = ja;
hasScannedForEncodings = 0;
knownRegions を検索する
knownRegions = (
        en,
        Base,
        );

上記のコードを以下に変更する

knownRegions = (
        ja,
        en,
        Base,
        );
ファイルを保存して、再度 Xcode を開く:

Development Language が日本語に変更されているのをご確認いただけます

スクリーンショット 2020-05-24 16.13.36.png

新しい言語の追加

  1. ファイルナビゲーター内のプロジェクトファイルをクリックします
  2. プロジェクトファイルをクリックします
  3. 情報タブを開いていることを確認してください
  4. Localizations セクションのプラスボタンをクリックしてください
  5. 追加したい言語を選択してください

スクリーンショット 2020-05-24 16.15.42.png

また、必ずスイッチ Use Base Internationalization を有効にしてください

スクリーンショット 2020-05-24 16.53.57.png

ストーリーボードのローカリゼーション

初期設定

ローカリゼーションを最初にセットアップするとき、Xcodeはあなたのためにすべての文字列を抽出してそれらをファイルに入れる作業を行います。ストーリーボードのローカリゼーションをセットアップするには:

  1. Storyboard ファイルを選択します。
  2. 右側のパネルで、Localizations セクションを探し、追加したい言語のオプションをオンにします。

スクリーンショット 2020-05-24 16.18.53.png

これで、左側のファイルナビゲーターで、Storyboard ファイルを展開し、翻訳ファイルを確認できます:

スクリーンショット 2020-05-24 16.20.10.png

Main.strings (English) をクリックすると、原文と翻訳文のペアが表示されます:

/* Class = "UILabel"; text = "こんにちは"; ObjectID = "ok2-tj-BN9"; */
"ok2-tj-BN9.text" = "こんにちは";

さて、ok2-tj-BN9 は、Storyboard 上にあるUILabel の要素IDです。ストーリーボードインターフェースに戻り、そのラベルを選択すると、その要素IDが表示されます:

スクリーンショット 2020-05-24 16.22.14.png

また、.text はその UILabel のテキストプロパティを示しているにすぎません。

さぁ、これで翻訳済みのストリングを Main.strings (English) に入れられるようになりました:

/* Class = "UILabel"; text = "こんにちは"; ObjectID = "ok2-tj-BN9"; */
"ok2-tj-BN9.text" = "Hello";

トランスレーションの追加

さらにトランスレーションを追加したい場合。 UIStoryboard でエレメントのオブジェクトIDを見つけ、それを Main.strings (English) ファイルに追加する。

エレメントが異なる場合、プロパティは同じではない:

//9rM-vm-U1g is an UITextField. Placeholder means placeholder.
"9rM-vm-U1g.placeholder" = "ユーザー名";

//GsY-2N-hIy is an UIButton, normalTitle means the title of the button at normal.
"GsY-2N-hIy.normalTitle" = "ボタン";

//BYZ-38-t0r is an UIViewController
"BYZ-38-t0r.title" = "登録画面";

文字列のローカライズ

文字列ファイルを作成する

command-Nを押し、Strings file を作成する

スクリーンショット 2020-05-24 16.33.11.png

ファイル Localizable.strings に名前を付ける

Xcodeでそのファイルを選択し、右側のローカライズボタンを選択する。ポップアップで英語を選択する。次に、右側のパネルで、すべての言語を選択する。

スクリーンショット 2020-05-24 16.35.48.png

全ての文字列を NSLocalizedString に入れて下さい

この例では、元のコードは以下です:

@IBAction func actionPress(){
    let alert = UIAlertController(title: "こんにちは", message: nil, preferredStyle: .alert)
    let actionClose = UIAlertAction(title: "はい", style: .cancel, handler: nil)
    alert.addAction(actionClose)
    present(alert, animated: true, completion: nil)
}
@IBAction func actionPress(){
    let alert = UIAlertController(title: NSLocalizedString("こんにちは", comment: ""), message: nil, preferredStyle: .alert)
    let actionClose = UIAlertAction(title: NSLocalizedString("はい", comment: ""), style: .cancel, handler: nil)
    alert.addAction(actionClose)
    present(alert, animated: true, completion: nil)
}

コードファイルから文字列を抽出する

  1. Terminal を開く
  2. cd コマンドで .lproj ファイルがある場所に移動し、以下のコマンドを実行する
find . -name \*.swift | xargs genstrings -o en.lproj
find . -name \*.swift | xargs genstrings -o ja.lproj

en.lproj ファイルを開く:

ご覧の通り、すでにプログラムの文字列が含まれています:

/* No comment provided by engineer. */
"こんにちは" = "こんにちは";

/* No comment provided by engineer. */
"はい" = "はい";

これで、英語の訳を追加することができます:

/* No comment provided by engineer. */
"こんにちは" = "Hello";

/* No comment provided by engineer. */
"はい" = "Yes";

ローカリゼーションのデバッグ

シミュレーターの言語を変更する

シミュレーターの言語を変更するには、Edit Schemeをクリックします。

スクリーンショット 2020-05-24 16.45.10.png

Options タブを選択すると、Application Language を編集できるようになります。

スクリーンショット 2020-05-24 16.45.54.png

プログラムを英語で実行すると次のようになります:

スクリーンショット 2020-05-24 16.45.10.png

ローカライズされていない文字列の確認方法:

再度、設定 Edit Scheme のタブ Options を開き、Show non-localized strings をオンに切り替える。

スクリーンショット 2020-05-24 16.49.09.png

この状態でプログラムをテストする。ローカライズされていない文字列があれば、コンソールにエラーが表示される:

スクリーンショット 2020-05-24 16.57.13.png

Like this message:

localization[3921:116986] [strings] ERROR: pBD-R7-cpO.text not found in table Main of bundle CFBundle 0x7fdab8704210

実際のデバイスでアプリケーションの言語を変更する:

実際のデバイスでアプリケーションをテストしてから、アプリケーションの言語を切り替える必要がある。そのためには:

  1. システム設定を開く

  2. アプリの名前を見つけ、アプリの設定を開く

  3. 言語オプションをクリックする

  4. 言語を切り替える

  5. これで、自分のアプリをテストすることが可能となります。

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

Ionic + Firebase Firestoreの中身を表示するアプリ

はじめに

Ionicを使ってFirestoreに登録したデータを表示するアプリを作成します。
表示するのは趣味で運営しているスノーボードまとめサイトの記事です。
スノーボードまとめサイトTWEAK

以下のようにFirestoreに登録した記事の情報を一覧表示して、クリックすると記事に遷移するアプリを作成します。
firebaseList.gif

プロジェクトを作成する

以下のコマンドでプロジェクトを作成します。

ionic start firestoreList blank --type=angular

必要なパッケージのインストールと環境設定

Ionicのアプリ内でwebページを開くためにIn App Browserをインストールします。

ionic cordova plugin add cordova-plugin-inappbrowser
npm install @ionic-native/in-app-browser

Firebaseと@angular/fireをインストールします。

npm install firebase @angular/fire --save

以下のように環境設定します。

/src/environments/environment.ts
export const environment = {
  production: false,
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>'
  }
};

Firestoreにデータを登録する

以下のようにデータを登録しました。
アプリではgroundtricksコレクションの中身を一覧表示します。
スクリーンショット 2020-05-23 16.46.20.png

実装

app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';

//Firebase設定情報ファイルをインポート
import { environment } from '../environments/environment';

//Firebaseを利用するためのモジュール
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, 
    IonicModule.forRoot(), 
    AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFirestoreModule
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    InAppBrowser
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
app/home/home.page.html
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      記事一覧
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-card *ngFor="let trick of groundtricks" class="activated">
    <ion-item (click)="openPage(trick.url)" class="activated">
    <ion-card-header>
      <ion-img src = "{{trick.image}}" class="image"></ion-img>
    </ion-card-header>
      <ion-card-content>
          <p><font size="2">レベル:{{ trick.level }}</font></p><br>
          <p><font size="3" color="#000000">{{ trick.name }}</font></p>
      </ion-card-content>
    </ion-item>
  </ion-card>
</ion-content>
app/home/home.page.ts
import { Component, OnInit } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit{
  groundtricks: any;

  constructor(
    private firestore: AngularFirestore,
    private inAppBrowser: InAppBrowser,
  ) {}

  ngOnInit() {
    this.readGroundtrick().subscribe(data => {
      this.groundtricks = data.map(e => {
        return {
          genre: e.payload.doc.data()['genre'],
          image: e.payload.doc.data()['image'],
          level: e.payload.doc.data()['level'],
          rotation: e.payload.doc.data()['rotation'],
          name: e.payload.doc.data()['name'],
          url: e.payload.doc.data()['url'],
        };
      });
    });
  }

  openPage(url){
    this.inAppBrowser.create(url);
  }

  readGroundtrick() {
    return this.firestore.collection('groundtrick').snapshotChanges();
  }
}

iOSアプリとして出力

Cordovaの場合は、以下のコマンドでiOSアプリとして出力します。

ionic cordova prepare ios

おわりに

IonicとFirebaseで簡単なiOSアプリを開発してみました。何もしなくてもダークモードに対応していて感動しました。
今後、追加機能の開発についても紹介していきます。

参考

AngularのプロジェクトにFirebaseを導入する
iOSでの開発 - Ionic Framework 日本語ドキュメンテーション

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

FlutterでWebRTCをやってみる with AgoraSDK

はじめに

WebRTC を使ったアプリを開発する機会があり、何かお手軽な package ないかなーと探していたところ FlutterWebRTC を手軽に利用できる plugin を見つけたので試しにアプリを作成してみました。

WebRTC とは

WebRTC (Web Real-Time Communication) とは、ビデオや音声、データをブラウザ間でやり取り可能にするための規格で、Google によってオープンソース化されました。 ユーザーはその API を経由することでリアルタイム通信を実現できます。

参考: WebRTCの基本とP2P通信が成立するまでを学ぶ

最近ではコロナの影響もあり、ウェブ会議システムやチャットツールなどの利用者が急増しています。

  • Zoom
  • Hang out
  • Discord
  • Microsoft Teams

スクリーンショット 2020-05-25 04.37.36.png

コロナをきっかけに一般に広く知られるようになり、今まで利用するに至らなかった勢が利用していました(周りでも)。
今後これらのツールが一般的に利用されるようになるのではないでしょうか。

Agora.IO SDK

Agora.IO が開発している、ビデオ通話やライブ配信を構築できる SDK です。
基盤となるこの SDK を利用して様々な言語やプラットフォームで利用することができます。
日本では NTT CommunicationsSkyWay に相当するものです。

Flutter では agora_rtc_engine | Flutter package を利用します。

実装

実際にサンプルアプリを実装していきます.

Agora Project の作成

Agora を利用するには Agora.IOProject を作成し、 AppID を入手する必要があります.
無料枠が 10000 minutes 程あるので当分は無料で利用できますし、無料枠を超えても金額を請求されることはないので安心してください.

まずはこちらから登録して Project を作成します.
Agora | Sign Up

Project を作成後、Project Management をクリックして作成した ProjectAppID をメモしておきます.

スクリーンショット 2020-05-21 18.21.36.png

Platform settings

Platform 毎に権限周りや固有の設定をしていきます.

iOS

ios/Runner/info.plist にカメラとマイクの権限を追加

info.plist
<key>NSCameraUsageDescription</key>
<string>Use camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>Use mic</string>
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

WebView を利用するので以下も追加

info.plist
<key>io.flutter.embedded_views_preview</key>
<true/>

Android

android/app/src/main/AndroidManifest.xml に以下の権限を追加します.

AndroidManifest.xml
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

android/app/src/proguard-rules.pro を作成し、以下を書いておきます
(難読化によるアプリクラッシュを防ぐ).

proguard-rules.pro
-keep class io.agora.**{*;}

platform 固有の設定は以上です.

agora_rtc_engine 周りの実装

ここがメインの部分になります.

agora_rtc_engine の追加

pubspeck.yaml
dependencies:
  agora_rtc_engine: ^1.0.12

Controller

今回は state_notifierfreezed パッケージを利用して実装しました。
agora_rtc_engine に関する処理は全部この中でやっています。

WebRtcController.dart
import 'package:agora_example/models/entities/entities.dart';
import 'package:agora_example/utils/constants.dart';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:state_notifier/state_notifier.dart';

import 'webrtc_state.dart';

class WebRtcController extends StateNotifier<WebRtcState> {
  WebRtcController({
    @required String roomName,
  }) : super(const WebRtcState()) {
    debugPrint('$tag: init()');
    initWebRtc(roomName: roomName);
  }

  static const tag = 'WebRtcController';

  Future<void> initWebRtc({@required String roomName}) async {
    /// initialize する前に必ず Permission Request を行う
    await Permission.camera.request();
    await Permission.microphone.request();

    /// 先程メモした AppID を利用
    await AgoraRtcEngine.create(Constants.appId);

    /// AV 周りに関する設定
    await AgoraRtcEngine.enableAudio();
    await AgoraRtcEngine.enableVideo();
    await AgoraRtcEngine.setChannelProfile(ChannelProfile.Communication);
    await AgoraRtcEngine.enableWebSdkInteroperability(true);

    /// Event Listener を設定する

    /// 自分が Join に成功した時
    AgoraRtcEngine.onJoinChannelSuccess = _onJoinChannelSuccess;

    /// 相手が Join に成功した時
    AgoraRtcEngine.onUserJoined = _onUserJoined;

    /// 自分が Leave した時
    AgoraRtcEngine.onLeaveChannel = _onLeaveChannel;

    /// 相手が Offline になった時
    AgoraRtcEngine.onUserOffline = _onUserOffline;

    /// Join する処理
    await AgoraRtcEngine.startPreview();
    await AgoraRtcEngine.joinChannel(null, roomName, null, 0);
  }

  Future<void> toggleLocalAudio() async {
    final localAvStatus = state.localAvStatus;
    await AgoraRtcEngine.muteLocalAudioStream(localAvStatus.mic);
    state = state.copyWith(
      localAvStatus: localAvStatus.copyWith(
        mic: !localAvStatus.mic,
      ),
    );
  }

  Future<void> toggleLocalVideo() async {
    final localAvStatus = state.localAvStatus;
    await AgoraRtcEngine.muteLocalVideoStream(localAvStatus.video);
    state = state.copyWith(
      localAvStatus: localAvStatus.copyWith(
        video: !localAvStatus.video,
      ),
    );
  }

  void switchView(int viewIndex) {
    state = state.copyWith(viewIndex: viewIndex);
  }

  void _onJoinChannelSuccess(String roomName, int uid, int elapsed) {
    debugPrint('$tag: onJoinChannelSuccess -> $uid');
    final users = [...state.users, WebRtcUser(uid: uid)];
    state = state.copyWith(
      users: users,
    );
  }

  void _onUserJoined(int uid, int elapsed) {
    debugPrint('$tag: onUserJoined -> $uid');
    final users = [...state.users, WebRtcUser(uid: uid)];
    state = state.copyWith(
      users: users,
    );
  }

  void _onLeaveChannel() {
    debugPrint('$tag: onLeaveChannel');
    state = state.copyWith(users: []);
  }

  void _onUserOffline(int uid, int reason) {
    debugPrint('$tag: onUserOffline -> $uid');
    final users = <WebRtcUser>[];
    for (final user in state.users) {
      if (user.uid != uid) {
        users.add(user);
      }
    }
    state = state.copyWith(users: users, viewIndex: 0);
  }

  @override
  void dispose() {
    super.dispose();
    /// agora_rtc_engine の破棄
    AgoraRtcEngine.leaveChannel();
    AgoraRtcEngine.stopPreview();
    AgoraRtcEngine.destroy();
  }
}


View

Agora からは PlatformView が提供されるのでそれを利用します。

webrtc_view.dart
AgoraRenderWidget(
    uid, /// Join に成功した場合に取得できる uid
    local: true, /// 自分であれば true, それ以外は false
    preview: true,
    mode: VideoRenderMode.Hidden /// object-fit か object-cover か
)

Page

webrtc_page.dart
import 'package:agora_example/pages/webrtc_page/room_user_list.dart';
import 'package:agora_example/pages/webrtc_page/webrtc_view.dart';
import 'package:agora_example/pages/webrtc_page/call_action_button.dart';
import 'package:agora_example/models/models.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';

class WebRtcPage extends StatelessWidget {
  const WebRtcPage._({Key key}) : super(key: key);

  static Widget wrapped({@required String roomName}) {
    return MultiProvider(
      providers: [
        StateNotifierProvider<WebRtcController, WebRtcState>(
          create: (context) => WebRtcController(roomName: roomName),
        ),
      ],
      child: const WebRtcPage._(),
    );
  }

  @override
  Widget build(BuildContext context) {
    final localAvStatus = context.select(
      (WebRtcState state) => state.localAvStatus,
    );
    return Scaffold(
      appBar: null,
      body: Stack(
        children: [
          Column(
            children: [
              SizedBox(
                height: MediaQuery.of(context).size.height / 2,
                child: const WebRtcView(),
              ),
              SizedBox(
                height: MediaQuery.of(context).size.height / 2,
                child: const RoomUserList(),
              ),
            ],
          ),
          Align(
            alignment: Alignment.bottomCenter,
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 30),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  CallActionButton(
                    tag: 'mic mute',
                    icon: localAvStatus.mic ? Icons.mic : Icons.mic_off,
                    color: Colors.black,
                    backgroundColor: Colors.white,
                    onPressed: () {
                      context.read<WebRtcController>().toggleLocalAudio();
                    },
                  ),
                  CallActionButton(
                    tag: 'call end',
                    icon: Icons.call_end,
                    backgroundColor: Colors.red,
                    onPressed: Navigator.of(context).pop,
                  ),
                  CallActionButton(
                    tag: 'video mute',
                    icon: localAvStatus.video
                        ? Icons.videocam
                        : Icons.videocam_off,
                    color: Colors.black,
                    backgroundColor: Colors.white,
                    onPressed: () {
                      context.read<WebRtcController>().toggleLocalVideo();
                    },
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

Demo

予め iPad の方を起動しておき、同じ RoomJoin しています。

映像の遅延もなく、動作もサクサクで快適に動作しました。
Flutter で手軽に WebRTC を利用することができるのは嬉しいですね。

終わりに

今回作成したサンプルアプリは GitHub に公開しているのでご自由にお使いください。

yukitaka13-1110 / flutter_webrtc_agora_example

コロナウイルスの影響もあってリモートワークやその他遠隔コミュニケーションでビデオ通話ができるツールがの需要が高まってきているのを感じているので、その裏側を作るのも面白そうだなと思いました。

 

現在、QUANDO では iOS, Android, Flutter エンジニア募集中です ← ここ重要

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

xcodeでiosのアイコンを設定する, Images.xcassets/AppIcon.appiconset

xcodeでのアイコンの設定が分かりづらかったので方法を書きます。

サイズのあった.pngをprojectのImages.xcassets/AppIcon.appiconset/に置きます。そして、Contents.jsonを書きます。

Images.xcassets/AppIcon.appiconset/icon_1024x1024.pngを用意して、以下のスクリプトを実行すると、imagamagickでアイコンを作成します。

icon.zsh
#!/bin/zsh

# Images.xcassets/AppIcon.appiconset/
# use : icon_1024x1024.png

mogrify -resize 20x20! icon_1024x1024.png icon-20.png
mogrify -resize 40x40! icon_1024x1024.png icon-20@2x.png
mogrify -resize 60x60! icon_1024x1024.png icon-20@3x.png
mogrify -resize 80x80! icon_1024x1024.png icon-40@2x.png
mogrify -resize 120x120! icon_1024x1024.png icon-40@3x.png
mogrify -resize 180x180! icon_1024x1024.png icon-60@3x.png

cp -rf icon-20@2x.png icon-40.png
cp -rf icon-20@3x.png icon-60.png
cp -rf icon-40@3x.png icon-60@2x.png

mogrify -resize 29x29! icon_1024x1024.png icon-29.png
mogrify -resize 58x58! icon_1024x1024.png icon-29@2x.png
mogrify -resize 87x87! icon_1024x1024.png icon-29@3x.png
mogrify -resize 76x76! icon_1024x1024.png icon-76.png
mogrify -resize 152x152! icon_1024x1024.png icon-76@2x.png
mogrify -resize 167x167! icon_1024x1024.png icon-83.5@2x.png

if [ ! -f ./Contents.json ];then
echo '
{
  "images" : [
    {
      "filename" : "icon-20@2x.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "filename" : "icon-20@3x.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "20x20"
    },
    {
      "filename" : "icon-29@2x.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "filename" : "icon-29@3x.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "29x29"
    },
    {
      "filename" : "icon-40@2x.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "filename" : "icon-40@3x.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "40x40"
    },
    {
      "filename" : "icon-60@2x.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "60x60"
    },
    {
      "filename" : "icon-60@3x.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "60x60"
    },
    {
      "filename" : "icon-20.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "20x20"
    },
    {
      "filename" : "icon-20@2x.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "filename" : "icon-29.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "29x29"
    },
    {
      "filename" : "icon-29@2x.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "filename" : "icon-40.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "40x40"
    },
    {
      "filename" : "icon-40@2x.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "filename" : "icon-76.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "76x76"
    },
    {
      "filename" : "icon-76@2x.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "76x76"
    },
    {
      "filename" : "icon-83.5@2x.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "83.5x83.5"
    },
    {
      "filename" : "icon_1024x1024.png",
      "idiom" : "ios-marketing",
      "scale" : "1x",
      "size" : "1024x1024"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}' > ./Contents.json
fi
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む