- 投稿日:2020-05-25T12:51:39+09:00
[Xcode]The maximum number of apps for free development profiles has been reachedと出てインストールできない。
原因
自作のアプリやプロビジョニングプロファイルを削除しても、インストールに失敗する場合は無料枠プロファイルのカウント処理がバグっている可能性が高いです。
対応
iPhoneをMacに繋いで、コンソールでログを表示しながら再度インストールを試してください。
- Window->Devices and Simulators->対象のデバイスを選択してOpen Consoleボタンをクリックでログを表示
- アプリを実行して対象デバイスへのインストールを試みてください。
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>" )}というようなエラーが出ているかと思います。
- その一覧に表示されているアプリを削除または、再インストール
- 自作アプリをインストール
の順番で実行してみてください。
情報源
3日ほど悩んだので取り急ぎ共有しました。
- 投稿日:2020-05-25T08:01:04+09:00
iOSアプリのローカライズとローカリゼーションのデバッグ - 開発言語の変更、ストーリーボードと文字列のローカライズ、ローカライズされていない文字列の特定とデバッグ
ローカリゼーションとは?
自分が作ったアプリが人気になり、英語など他の言語にそのアプリを翻訳してもっと多くの人が使えるようにしたいと思うときが来るかもしれません。iOSのシステムは、ユーザー設定に基づきコンテンツを適切な言語で表示するなど、大方のローカリゼーションを行ってくれます。
アプリをローカライズする方法
Development language
の変更開発言語とは、アプリの開発時に使用する言語のことを指します。まず、設定の内容を確認する必要があります:
プロジェクトファイルをクリックし、(ターゲットではなく)プロジェクトファイルを選択します。ローカリゼーションのセクションで、
Development Language
が日本語なのか英語なのかを確認します。日本語の場合、設定は正しいので、何もする必要はありません。英語の場合、引き続き開発言語の変更のガイドに従ってください。次に
Xcode
を閉じ、.xcodeproj
ファイルを右クリックして選択し、このファイルの内容を表示する:
project.pbxproj
ファイルを見つけ、テキストエディターで開く。
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
が日本語に変更されているのをご確認いただけます新しい言語の追加
- ファイルナビゲーター内のプロジェクトファイルをクリックします
- プロジェクトファイルをクリックします
- 情報タブを開いていることを確認してください
Localizations
セクションのプラスボタンをクリックしてください- 追加したい言語を選択してください
また、必ずスイッチ
Use Base Internationalization
を有効にしてくださいストーリーボードのローカリゼーション
初期設定
ローカリゼーションを最初にセットアップするとき、Xcodeはあなたのためにすべての文字列を抽出してそれらをファイルに入れる作業を行います。ストーリーボードのローカリゼーションをセットアップするには:
- Storyboard ファイルを選択します。
- 右側のパネルで、
Localizations
セクションを探し、追加したい言語のオプションをオンにします。これで、左側のファイルナビゲーターで、Storyboard ファイルを展開し、翻訳ファイルを確認できます:
Main.strings (English)
をクリックすると、原文と翻訳文のペアが表示されます:/* Class = "UILabel"; text = "こんにちは"; ObjectID = "ok2-tj-BN9"; */ "ok2-tj-BN9.text" = "こんにちは";さて、
ok2-tj-BN9
は、Storyboard 上にあるUILabel
の要素IDです。ストーリーボードインターフェースに戻り、そのラベルを選択すると、その要素IDが表示されます:また、
.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
を作成するファイル
Localizable.strings
に名前を付けるXcodeでそのファイルを選択し、右側のローカライズボタンを選択する。ポップアップで英語を選択する。次に、右側のパネルで、すべての言語を選択する。
全ての文字列を
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) }コードファイルから文字列を抽出する
- Terminal を開く
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をクリックします。
Options
タブを選択すると、Application Language
を編集できるようになります。プログラムを英語で実行すると次のようになります:
ローカライズされていない文字列の確認方法:
再度、設定
Edit Scheme
のタブOptions
を開き、Show non-localized strings
をオンに切り替える。この状態でプログラムをテストする。ローカライズされていない文字列があれば、コンソールにエラーが表示される:
Like this message:
localization[3921:116986] [strings] ERROR: pBD-R7-cpO.text not found in table Main of bundle CFBundle 0x7fdab8704210実際のデバイスでアプリケーションの言語を変更する:
実際のデバイスでアプリケーションをテストしてから、アプリケーションの言語を切り替える必要がある。そのためには:
システム設定を開く
アプリの名前を見つけ、アプリの設定を開く
言語オプションをクリックする
言語を切り替える
これで、自分のアプリをテストすることが可能となります。
- 投稿日:2020-05-25T07:33:20+09:00
Ionic + Firebase Firestoreの中身を表示するアプリ
はじめに
Ionicを使ってFirestoreに登録したデータを表示するアプリを作成します。
表示するのは趣味で運営しているスノーボードまとめサイトの記事です。
スノーボードまとめサイトTWEAK以下のようにFirestoreに登録した記事の情報を一覧表示して、クリックすると記事に遷移するアプリを作成します。
プロジェクトを作成する
以下のコマンドでプロジェクトを作成します。
ionic start firestoreList blank --type=angular必要なパッケージのインストールと環境設定
Ionicのアプリ内でwebページを開くためにIn App Browserをインストールします。
ionic cordova plugin add cordova-plugin-inappbrowser npm install @ionic-native/in-app-browserFirebaseと@angular/fireをインストールします。
npm install firebase @angular/fire --save以下のように環境設定します。
/src/environments/environment.tsexport 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コレクションの中身を一覧表示します。
実装
app/app.module.tsimport { 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.tsimport { 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 日本語ドキュメンテーション
- 投稿日:2020-05-25T07:12:39+09:00
FlutterでWebRTCをやってみる with AgoraSDK
はじめに
WebRTC
を使ったアプリを開発する機会があり、何かお手軽なpackage
ないかなーと探していたところFlutter
でWebRTC
を手軽に利用できるplugin
を見つけたので試しにアプリを作成してみました。WebRTC とは
WebRTC
(Web Real-Time Communication) とは、ビデオや音声、データをブラウザ間でやり取り可能にするための規格で、API
を経由することでリアルタイム通信を実現できます。最近ではコロナの影響もあり、ウェブ会議システムやチャットツールなどの利用者が急増しています。
- Zoom
- Hang out
- Discord
- Microsoft Teams
コロナをきっかけに一般に広く知られるようになり、今まで利用するに至らなかった勢が利用していました(周りでも)。
今後これらのツールが一般的に利用されるようになるのではないでしょうか。Agora.IO SDK
Agora.IO
が開発している、ビデオ通話やライブ配信を構築できるSDK
です。
基盤となるこのSDK
を利用して様々な言語やプラットフォームで利用することができます。
日本ではNTT Communications
のSkyWay
に相当するものです。
Flutter
では agora_rtc_engine | Flutter package を利用します。実装
実際にサンプルアプリを実装していきます.
Agora Project
の作成
Agora
を利用するにはAgora.IO
にProject
を作成し、AppID
を入手する必要があります.
無料枠が10000 minutes
程あるので当分は無料で利用できますし、無料枠を超えても金額を請求されることはないので安心してください.まずはこちらから登録して
Project
を作成します.
Agora | Sign Up
Project
を作成後、Project Management
をクリックして作成したProject
のAppID
をメモしておきます.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.yamldependencies: agora_rtc_engine: ^1.0.12Controller
今回は
state_notifier
とfreezed
パッケージを利用して実装しました。
agora_rtc_engine
に関する処理は全部この中でやっています。WebRtcController.dartimport '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.dartAgoraRenderWidget( uid, /// Join に成功した場合に取得できる uid local: true, /// 自分であれば true, それ以外は false preview: true, mode: VideoRenderMode.Hidden /// object-fit か object-cover か )Page
webrtc_page.dartimport '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
の方を起動しておき、同じRoom
にJoin
しています。映像の遅延もなく、動作もサクサクで快適に動作しました。
Flutter
で手軽にWebRTC
を利用することができるのは嬉しいですね。終わりに
今回作成したサンプルアプリは GitHub に公開しているのでご自由にお使いください。
yukitaka13-1110 / flutter_webrtc_agora_example
コロナウイルスの影響もあってリモートワークやその他遠隔コミュニケーションでビデオ通話ができるツールがの需要が高まってきているのを感じているので、その裏側を作るのも面白そうだなと思いました。
現在、
QUANDO
ではiOS, Android, Flutter
エンジニア募集中です ← ここ重要
- 投稿日:2020-05-25T04:58:26+09:00
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