20210429のiOSに関する記事は6件です。

[SwiftUI]navigationBarItemsにUI部品を表示する方法(初学者向け)

import SwiftUI struct ContentView: View { var body: some View { // Navigationの設定 NavigationView { Text("SwiftUI始めました") .navigationBarItems(trailing: Text("ナビゲーションバーの右側に配置")) } } } ナビゲーションバーの右側にUI部品を配置する場合はtrailingを、左側の場合はleadingを指定し設定する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityAdsでATT対応

環境 MacOS Catalina 10.15.7 Unity 2019.4.17 Advertisement 3.5.2 ※UnityAds Xcode 12.2 UnityAdsで対応が必要な手順 基本的には以下のガイドラインに沿って対応します。 https://unityads.unity3d.com/help/resources/ios14-publisher-guide 1. UnityAdsのパッケージAdvertisementを3.5.1以降にする 2. Info.plist に Privacy - Tacking Usage Description を追記する 3. AppTrackingTransparency.frameworkを参照するように設定 1. UnityAdsのパッケージAdvertisementを3.5.1以降にする Window > Package Manager > Unity Registty > Advertisement UnityAdsに特有なところはここだけです。 SKAdNetworkIdentifierなどはビルド時に自動で追加されてるはずなので改めて対応する必要はありません。 2. Info.plist に Privacy - Tacking Usage Description を追記する 3. AppTrackingTransparency.frameworkを参照するように設定 2,3に関しては以下の記事が参考になりました。 ただし、一部記載内容に誤りがあるので注意してください。 ATT対応しないとiOSの審査に通らないらしいので、未対応の人は対応しておくと良いかと。僕はこのページで対応した。Action -> Action<int>https://t.co/SsxYzpcAyGimport文#import <string.h>#import <AppTrackingTransparency/AppTrackingTransparency.h>https://t.co/dPbvdx0VCu https://t.co/xyVIvaU7RK— ジウ@ゲーム開発者の本音 (@JiuGameEngineer) April 27, 2021 その他 許可を求める文言にすると審査でリジェクトされるらしいです。 ネイティブのダイアログを表示する直前にクッションでお願いをする画面を表示するアプリもあります。ただし、自分の場合はゲーム開始直後のユーザーに表示する画面を極力減らしたいので見送りました。 やるのが面倒だったわけではない 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityAdsで ATT - App Tracking Transparency

環境 MacOS Catalina 10.15.7 Unity 2019.4.17 Advertisement 3.5.2 ※UnityAds Xcode 12.2 UnityAdsで対応が必要な手順 基本的には以下のガイドラインに沿って対応します。 https://unityads.unity3d.com/help/resources/ios14-publisher-guide 1. UnityAdsのパッケージAdvertisementを3.5.1以降にする 2. Info.plist に Privacy - Tacking Usage Description を追記する 3. AppTrackingTransparency.frameworkを参照するように設定 1. UnityAdsのパッケージAdvertisementを3.5.1以降にする Window > Package Manager > Unity Registty > Advertisement UnityAdsに特有なところはここだけです。 SKAdNetworkIdentifierなどはビルド時に自動で追加されてるはずなので改めて対応する必要はありません。 2. Info.plist に Privacy - Tacking Usage Description を追記する 3. AppTrackingTransparency.frameworkを参照するように設定 追記 中身を検証してませんが、こんなものがありました。 これで済むならこちらで良さそうです。 https://alanyeats.com/post/unityapptrackingtransparencypopup/ 2,3に関しては以下の記事が参考になりました。 ただし、一部記載内容に誤りがあるので注意してください。 ATT対応しないとiOSの審査に通らないらしいので、未対応の人は対応しておくと良いかと。僕はこのページで対応した。Action -> Action<int>https://t.co/SsxYzpcAyGimport文#import <string.h>#import <AppTrackingTransparency/AppTrackingTransparency.h>https://t.co/dPbvdx0VCu https://t.co/xyVIvaU7RK— ジウ@ゲーム開発者の本音 (@JiuGameEngineer) April 27, 2021 その他 許可を求める文言にすると審査でリジェクトされるらしいです。 ネイティブのダイアログを表示する直前にクッションでお願いをする画面を表示するアプリもあります。ただし、自分の場合はゲーム開始直後のユーザーに表示する画面を極力減らしたいので見送りました。 やるのが面倒だったわけではない 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ios admobで、GADInvalidInitializationExceptionが消えなくて泣きそうだった話

admobに登録してあるアプリのメンテナンスしようとして、 を参考に作業してみようかなと思って pod update の後、buildしてみると、 Terminating app due to uncaught exception 'GADInvalidInitializationException', reason: 'The Google Mobile Ads SDK was initialized without AppMeasurement. Google AdMob publishers, follow instructions here: https://googlemobileadssdk.page.link/admob-ios-update-plist to include the AppMeasurement framework and set the -ObjC linker flag という例外でアプリがクラッシュしまくる。 言われた通りにInfo.plistを編集してるだけなのだが。。。 ググりまくったら、以下の解決法を発見。 これで良いのかわからないけど、とりあえず動くようになった。 解決法 Info.plistに <key>GADIsAdManagerApp</key> <true/> を追加。 参考リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReadableContentGuideを利用してUICollectionViewのコンテンツを見やすくする

環境 Swift 5.4 Xcode 12.5 概要 ReadableContentGuide(以下RCG) は、端末によって コンテンツの読みやすい幅を実現するために役立つ UILayoutGuide です。 Apple Developer ドキュメント - readableContentGuide デザインによるコンテンツのマージン指定が特になければ、 UIDevice.current.userInterfaceIdiom を判定して iPhoneとiPadそれぞれに制約を設けることをせず、 RCGを利用して良い感じにマージンを設定することができます。 UITableViewやUIScrollViewで利用している例はちょこちょこ見かける気がするので、 今回はUICollectionViewの画面でRCGを利用した例を紹介します。 ※注意 Storyboradは利用せず、コードでの実装です。 実装 UICollectionViewに対してRCGを適用する UICollectionViewの制約をかける際に、 親Viewの SafeAreaLayoutGuide ではなくRCGを基準とします。 そのためitemSizeを算出する時は、RCGの layoutFrame.width を利用します。 コンテンツ上下左右のマージンはRCGに任せた実装になります。 import UIKit final class ReadableCollectionViewController: UIViewController { private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) private let itemSpacing: CGFloat = 10 override func loadView() { super.loadView() view.backgroundColor = .systemBackground } override func viewDidLoad() { super.viewDidLoad() collectionView.backgroundColor = .secondarySystemBackground collectionView.delegate = self collectionView.dataSource = self collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell") collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.readableContentGuide.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.readableContentGuide.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor) ]) } } extension ReadableCollectionViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 30 // 任意の数 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) cell.backgroundColor = .gray return cell } } extension ReadableCollectionViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let cellSideLength: CGFloat = (view.readableContentGuide.layoutFrame.width - itemSpacing * 2) / 3 return .init(width: cellSideLength, height: cellSideLength) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { .zero } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { itemSpacing } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { itemSpacing } } 画面キャプチャ インジケータがコンテンツと被ってしまっているのでinsetの設定はあった方が良さそうです。 また、CollectionViewと親Viewの背景色が異なる場合は注意が必要です。 iPhone8 iPadPro(12.9-inch) UICollectionViewのセクションに対してRCGを適用する 先の実装とは異なり、UICollectionViewの制約は、 親Viewの SafeAreaLayoutGuide を基準とします。 その代わりにSectionのInsetを設定する箇所で 計算処理を追加しています。 ※ 先の実装と変更がない箇所は処理を省略しています import UIKit final class ReadableCollectionViewController: UIViewController { // 先の実装と同様 override func viewDidLoad() { super.viewDidLoad() // 先の実装と同様 NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) ]) } } extension ReadableCollectionViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // 先の実装と同様 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { // 先の実装と同様 } } extension ReadableCollectionViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // 先の実装と同様 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { let topOrBottomInset: CGFloat = (view.frame.height - view.readableContentGuide.layoutFrame.height) / 2 let leftOrRightInset: CGFloat = (view.frame.width - view.readableContentGuide.layoutFrame.width) / 2 return .init(top: topOrBottomInset, left: leftOrRightInset, bottom: topOrBottomInset, right: leftOrRightInset) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { // 先の実装と同様 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { // 先の実装と同様 } } 画面キャプチャ CollectionViewが画面全体に広がっているのでインジケータは見やすくなりました。 Sectionが複数になった時にtopとbottomのマージン設定を改善しないといけなさそうです。 iPhone8 iPadPro(12.9-inch) 後記 今回実装した画面は動的にitemのサイズが切り替わらないので UICollectionViewFlowLayoutのインスタンスをCollectionViewに渡して実現しようとしましたが、 viewWillAppear(_:) まではRCGのlayoutFrameの正確な値を 取得できなかったため、Delegateを利用しています。 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) print("viewWillAppear:\(view.readableContentGuide.layoutFrame)") } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() print("viewWillLayoutSubviews:\(view.readableContentGuide.layoutFrame)") } // iPhone8の場合 // viewWillAppear:(0.0, 0.0, 375.0, 667.0) // viewWillLayoutSubviews:(16.0, 20.0, 343.0, 647.0)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIでPHPhotoPickerを使用する

この記事は何? PHPhotoPickerをSwiftUIで使用するアプリを作成します。 実行環境 Swift 5.3 Xcode 12.5 macOS 11.3 Photosフレームワーク 「デバイスに保存された写真」を取得するための、プライバシーに配慮した全く新しいフレームワークです。 ハンズオン 順を追って、アプリを作成していきます。 基本の画面 基本画面には「写真を表示するための枠」と「写真ピッカーを開くためのボタン」を配置します。 まずは、「写真を表示する枠」のビューを作成しておきます。 PhotoFrame.swift import SwiftUI struct PhotoFrame: View { @Binding var image: Image? var body: some View { if let image = image { image .resizable() .aspectRatio(contentMode: .fit) } else { Image(systemName: "photo") .font(.title) .foregroundColor(.gray) } } } 次に、「写真ピッカーを開くためのボタン」を作成します。 ShowPickerButton.swift import SwiftUI struct ShowPickerButton: View { @Binding var isShowLibrary: Bool var body: some View { Button(action: { isShowLibrary = true }, label: { Text("Choose Photo") .font(.title2) .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10.0) }) } } こららのビューを基本画面に配置します。 ContentView.swift import SwiftUI struct ContentView: View { @State private var isShowPhotoLibrary = false @State var image: Image? @EnvironmentObject var classifier: ImageClassifier var body: some View { ZStack { PhotoFrame(image: $image) VStack { Spacer() Text(classifier.classificationLabel) ShowPickerButton(isShowLibrary: $isShowPhotoLibrary) } } .sheet(isPresented: $isShowPhotoLibrary, content: { PhotoPicker(isPresented: $isShowPhotoLibrary, seledtedImage: $image) }) } } 写真ピッカーのビューを作成する UIKitのビューコントローラをSwiftUIで使用するは、ViewControllerRepresentableプロトコルに適合した構造体でラップします。 PhotoPicker.swift import SwiftUI import PhotosUI struct PhotoPicker: UIViewControllerRepresentable { @Binding var isPresented: Bool @Binding var seledtedImage: Image? func makeUIViewController(context: Context) -> PHPickerViewController { var configuration = PHPickerConfiguration() configuration.filter = .images let picker = PHPickerViewController(configuration: configuration) picker.delegate = context.coordinator return picker } func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { // do nothing. } } PhotoPicker型を定義する デリゲートメソッドを介してPhotoPickerViewControllerとやり取りするには、仲介役となるCoordinatorクラスを定義する必要があります。 PhotoPicker.swift コーディネーターを定義する このクラスは、PhotoPicker構造体のネスト型として定義します。 Coordinatorクラス import SwiftUI import PhotosUI struct PhotoPicker: UIViewControllerRepresentable { @Binding var isPresented: Bool @Binding var seledtedImage: Image? func makeUIViewController(context: Context) -> PHPickerViewController {...} func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { // do nothing. } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: PHPickerViewControllerDelegate { private let parent: PhotoPicker init(_ parent: PhotoPicker) { self.parent = parent } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { parent.isPresented = false let itemProvider = results.first?.itemProvider // itemProvider is NSItemProvider? Type. if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) { itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in if let uiImage = image as? UIImage { self.parent.seledtedImage = Image(uiImage: uiImage) } } } } } } 最後に WWDC2021で、PHPickerViewがSwiftUIネイティブになったらいいなー。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む