最近、数ヶ月で数本のiPhoneアプリを開発して公開しました。その際に、個人的な Tips がそこそこ溜まってきたので備忘録として記載しときます。一つ一つは小さなTipsですが毎回いざというときに検索して毎回調べてしまいます…
目次
- 1 デバイスの各種サイズを取得する
- 2 Textのフォントを指定する
- 3 戻るや閉じるの動作をプログラムで
- 4 起動時の初期化処理を描く場所
- 5 バックグラウンドからの起動、URLスキームからの起動の捕捉
- 6 Viewオブジェクトを角丸の枠で囲む
- 7 NavigationViewのヘッダを消す
- 8 ListのUIがなんか嫌(余白がある)
- 9 通信元コンテンツを変更したがHTTPS取得しても変わらない(Alamofire)
- 10 JSON を Codable オブジェクトに変換する
- 11 Codable オブジェクトを作りたいが変換元のJSONがルート直下がArrayな場合
- 12 UIView を SwiftUIで利用したい
- 13 AppStoreに公開したがアプリ紹介のアプリ言語が「英語(EN)」となる
- 14 AppStoreに公開されたがAdMobでリンクできない(検索で出てこない)
- 15 M1 Mac で Cocoapod周りでエラーがでる
デバイスの各種サイズを取得する
# デバイス全体サイズ
UIScreen.main.bounds.size
# 上部のセーフエリアのサイズ
UIApplication.shared.windows[0].safeAreaInsets
Textのフォントを指定する
Text("ほげほげ")
.font(.custom("Avenir-Roman", size: 20))
戻るや閉じるの動作をプログラムで
struct ExamplelView: View {
@Environment(\.presentationMode) var presentation
var body: some View {
...
self.presentation.wrappedValue.dismiss()
...
}
}
起動時の初期化処理を描く場所
@main
struct ExampleApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
Text("Hello World")
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// ここに初期化等処理を書く
return true
}
}
バックグラウンドからの起動、URLスキームからの起動の捕捉
@main
struct ExampleApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
SomeMainView()
.onOpenURL(perform: { url in
// URLスキームからの起動の場合、ここが呼び出される
})
}.onChange(of: scenePhase) { phase in
if phase == .active {
FireStorageManager.sentUserStats()
}
}
}
Viewオブジェクトを角丸の枠で囲む
SomeView()
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 2)
)
NavigationView {
VStack {
}.navigationBarTitle("hoge")
.navigationBarHidden(true)
}
navigationBarHidden を指定すればOKだが navigationBarTitle も指定しないと効かない…
ListのUIがなんか嫌(余白がある)
最近のバージョンからデフォルトがそうなったらしい
List {
...
}.listStyle(PlainListStyle())
通信元コンテンツを変更したがHTTPS取得しても変わらない(Alamofire)
OS側でよしなにキャッシュしている場合があるらしい。以下のコードを通信前に呼び出せばキャッシュが消える(最新を取得するようになる)
URLCache.shared.removeAllCachedResponses()
JSON を Codable オブジェクトに変換する
通信で取得したJSON文字列を Codable なオブジェクトに変換する方法
struct ExampleObject: Codable {
let persons: [Person]
}
struct Person: Codable {
let name: String
let age: Int
let addressString: String
// JSONのタグ名と異なキーとしたい場合は CodingKeys でマッピングさせる
enum CodingKeys: String, CodingKey {
case name
case age
case addressString = "address_string"
}
}
こういうオブジェクトを作って例えば、Alamofire のレスポンスを例にすると responseEntity が ExampleObject 型になる。
guard let data = response.data else {
return
}
guard let responseEntity = try? JSONDecoder().decode(ExampleObject.self, from: data) else {
return
}
Codable オブジェクトを作りたいが変換元のJSONがルート直下がArrayな場合
上の例 ExampleObject がそういうケースだった場合の例。以下のようなオマジナイを追加する。
struct ExampleObject: Codable {
let persons: [Person] // "persons" Key は実際にはJSONには存在しない
}
extension ExampleObject {
init(from decoder: Decoder) throws {
var persons: [Person] = []
var unkeyedContainer = try decoder.unkeyedContainer()
while !unkeyedContainer.isAtEnd {
let person = try unkeyedContainer.decode(Person.self)
persons.append(person)
}
self.init(persons: persons)
}
}
extension ExampleObject {
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for person in persons {
try container.encode(person)
}
}
}
UIView を SwiftUIで利用したい
たとえば、GoogleMap (GMSMapView) の場合を例に… 他のViewも同じ。 ViewControllerの場合は UIViewRepresentable を UIViewControllerRepresentable に変更して微修正。
import SwiftUI
import GoogleMaps
struct MapView: UIViewRepresentable {
typealias UIViewType = GMSMapView
let mapView = GMSMapView(frame: .zero)
func makeCoordinator() -> Coordinator {
Coordinator(mapView: mapView)
}
func makeUIView(context: Context) -> GMSMapView {
// 初期化処理などはココに書く
return mapView
}
func updateUIView(_ uiView: GMSMapView, context: Context) {
// 描画等の処理はココに書く
// 呼び出されるタイミングは SwiftUI のStateオブジェクト変化時など
}
final class Coordinator: NSObject, GMSMapViewDelegate {
init(mapView: GMSMapView) {
super.init()
mapView.delegate = self
}
// Delegate の処理はココに追加する
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
return false
}
func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
}
}
}
AppStoreに公開したがアプリ紹介のアプリ言語が「英語(EN)」となる
Appleに問い合わせたら
App Store 上で表示される言語情報は、App バンドルのローカリゼーションフォルダ (.lproj) によって決められます。App バンドル内でローカリゼーションが未設定もしくは無効になってしまう問題は、通常 Xcode のプロジェクトでのローカリゼーションの誤設定が原因です。
と返信をもらった。よくわからんかったが、以下のようにしたら解決した。
- XCodeにてプロジェクト設定 Info -> Localization で Japanese を追加する — ①
- 新規ファイルで String File タイプで作成し名称をInfoPlist.strings とする
- InfoPlist.string のプロパティで Japanese にチェックを入れる — ②
おそらくこれで問題が解決される。 InfoPlist.string が空ファイルなのが気持ち悪いのでよくある、Info.plistの設定値のローカライズ文言も私は加えておきます。
"NSLocationWhenInUseUsageDescription" = "現在地の利用を許可頂くと地図モードで現在地が素早く確認できたり、ルートの検索ができるようになります";
"NSUserTrackingUsageDescription" = "「許可」するとお客様に最適化された広告が表示されます";

AppStoreに公開されたがAdMobでリンクできない(検索で出てこない)
AdMobでアプリに対してAppStoreのアプリ設定(リンク)をする必要がある。マニュアルでは「AppStoreに公開後、数日から数週間でAdMobの設定画面から検索できるようになります。」とあるが、いくら待っても検索でリンクさせたいアプリがでてこない
google-admob-ads-sdk という Google Group にてリクエストのメッセージを投稿するとすぐに対応してくれる。私は以下のように書いた。
Subject: Can't find my app to link to admob
I have released three my apps on app store month ago, but I can't find that on admob for linking.
Here my app store links
https://apps.apple.com/jp/app/michinori/id1560954019
https://apps.apple.com/jp/app/tokyo%E9%A7%85%E6%8E%A2%E7%B4%A2/id1563783099
https://apps.apple.com/jp/app/the-%E5%AE%9D%E5%A1%9A/id1566879484
Thank you so much for helping
数時間後返信もらって瞬殺で問題が解決された。
M1 Mac で Cocoapod周りでエラーがでる
時代が追いついていないらしい。対処方法は
- XCodeのアプリの設定 TARGET -> Build Settings の Excluded Architectures の Debug / Release に 「Any iOS Simulator SDK = arm64」を登録する — ①
- Podfile に以下を追記する
target 'SampleApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for SampleApp
...
post_install do |installer|
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
end
end
- pod install コマンドは Rosetta を経由して起動したターミナルで行う — ②

以上です。まだまだ書ききれない色々な項目がありそうですので追って別記事で追記します。