- 投稿日:2020-01-04T23:21:21+09:00
iOSアプリのバージョンアップをApple Storeへ提出するメモ
iOSアプリのバージョンアップを Apple Store へ提出するときいつも悩みます。今回は個人メモをまとめて記事にしてみました。
審査提出までの手順
ポイント
- ブラウザから Apple Developer へのサインインで、6桁の数字でエラーになる場合は、キーボードを英字入力にしてください。
- 12/23〜27 は Apple Store も Xmas 休暇です(みんなも休もう)
- 私のアプリは顔を検出して黒塗りにする機能があるのですが、顔データに関するプライバシーポリシーが必要なのだそうです。
- 今回の戦い
12/21 審査提出
12/24 リジェクト(顔データに関するプライバシーポリシーがない)
12/29 再提出
12/30 リジェクト(プライバシーポリシーが不十分)
01/04 再提出
01/04 Ready for Sale(やたー!)まとめ
これまで審査でバイナリのリジェクトは一回もなく、すべてメタデータの再提出です。プライバシーポリシーは英語のみで出しているので難しいです。最近は YouTube にアップロードしてもいろいろ聞かれますよね。個人情報の取り扱いなど、プログラマーも知っておくべきでしょう。今回はとても勉強になりました。
- 投稿日:2020-01-04T21:45:35+09:00
RailsでiOSアプリのURI スキームに対応する
一瞬「あれっ...」ってなったので備忘録
TL; DR
routes.rb
内でダイレクトルーティングで定義すればOKバージョン
- Ruby:2.6.1
- Rails:5.2.3
タイトルにiOSと書いてますがAndroidも対応できます。ただのRoutingの話なので。
やりたいこと
- RailsがWebとモバイルアプリの両方のサーバサイドの役割を担っている
- モバイルアプリ内で、特定の画面に遷移させるURIスキームを使いたい
- メニューなどで表示されていないページへの遷移など
- 上記の例としてInstagramに「運営からのコメント」があり、チャットで操作説明を受ける画面に遷移させたいような目的があったとする
- ヘルパーリンク:
qa_chat
- URIスキーム:
instagram://qa_chat
解決策
routes.rb
routes.rb
で、以下のようなダイレクトルーティングを設定してあげれば良いです。routes.rbdirect :qa_chat do "instagram://qa_chat" endヘルパーリンク
Railsと同一のViewでヘルパーリンクを利用する場合は、以下のように記述します。
sample.erb<%= link_to qa_chat_url %>ちなみにダイレクトルーティングについて、Railsガイドでは以下の部分に記載があります。
(このURIスキームについての記載はありませんが、あくまで参考までに)
小ネタ
外部URLもroutes.rbで変数として定義できる
ダイレクトルーティングといえば、こちらの使い方のほうが一般的です。
よく使う外部URLを変数定義して、Railsアプリケーション内で使うことができます。
サービスLPのURLを記述する時は、個別にではなく、routes.rb
にまとめて記述するほうが管理しやすいです。example.rbdirect :homepage do "http://www.rubyonrails.org" end
- 投稿日:2020-01-04T21:27:33+09:00
iOS アプリ開発で登場する View 実装方法の関係性 (SwiftUI、Storyboard、Xib、UIKit)
Swift で iOS アプリを開発するにあたり View の実装方法を調べていたところ、Storyboard、Xib、UIKit といった用語の関係性が全然分からず非常に苦労しました。
これらの全体的な関係性が (自分にとって) 分かりやすくまとまっている記事などが全く見つからなかったので、現時点での自分の理解をまとめます。
※ iOS 開発初心者のため、間違いあればご指摘ください
結論 (ざっくりした分類)
SwiftUI、Storyboard、Xib、UIKit をざっくり分類すると以下の通りです。
分類 要素名 GUI or コード コンポーネント化 概要 SwfitUI SwiftUI コード ◯ iOS 13 以上で使える新しい実装方法 SwfitUI 登場以前 Storyboard GUI × GUI でアプリの View 全体を管理 Xib GUI ◯ GUI で View のコンポーネントを作成 UIKit コード ◯ Swift のコードで View 周辺を扱うフレームワーク 以下でそれぞれ説明していきます。
SwiftUI vs SwiftUI 登場以前
まず大きな分類として、Swift UI と Swift UI 登場以前の 2 種類の実装方法があります。
Xcode の最近のバージョンで新規プロジェクトを作成しようとすると、User Interface の選択肢に SwiftUI と Storyboard が並んでいます。
この選択肢は、SwiftUI を使うか、それ以前の実装方法を使うかを選ぶのとかなり近いです。SwiftUI
2019 年に登場した、iOS 13 以降でのみ利用可能な新しい UI 実装方法です。
コードで UI を実装することになります。
ざっくり言うと、Flutter での UI 実装と似ていると言われています。現時点では細かい UI の再現は苦手な場合もあるようです。
なお、SwiftUI と SwiftUI 登場以前の実装方法 (UIKit) は共存可能とのことです。
参考
SwiftUI 登場以前
さて、ここが一番難しかったのですが、SwiftUI 登場以前の UI の実装方法には以下の 3 つがあります。
- Stobyboard (GUI)
- Xib (GUI)
- UIKit (コード)
これらからどれか 1 つを選んで実装するのかと思いきや、そうではありません。
UIKit 単独で View を実装することも可能ですが、Storyboard や Xib と組み合わせて実装することも多いようです。以下、これら 3 つを簡単に説明していきます。
Storyboard
GUI でアプリの各ページの View や画面遷移 (Segue) を実装するものです。
Storyboard だけで View を全て実装することができます。
View のコンポーネント化は苦手で、View 全体の管理に適しているようです。
View とプログラム (UIViewController) の紐付けは GUI の操作で実施します。Xib
GUI で各ページの View やコンポーネントを作る方法です。
Storyboard が苦手とする、View の再利用に適しています。
Xib で作成した View のコンポーネントは Storyboard やコードから利用可能です。ちなみに、Storyboard や Xib を編集するための Xcode のエディタを Interface Builder と言います。
参考
- StoryboardのBestPracticeについて考える(2) 〜ビューの再利用〜
- iOS 開発で storyboard と xib をうまく使い分けるプラクティス
- Storyboard よりも Xib を使いたい理由
- xibファイルでビューを作成して、ストーリーボードやコードから利用する方法(Swift3編)
UIKit
UIKit 自体は、SwiftUI 登場以前の iOS アプリの View 開発全般を担うフレームワークです。
UIKit は View の構築だけでなく、View とプログラムの紐付け (UIViewController) なども含んでいます。
なので、Storyboard や Xib で View を構築する場合であっても、プログラムとの紐付けのために UIKit を使うことになります。Storyboard や Xib でできることは UIKit で何でもできます。
「UIKit で View を構築するのは大変なので、Storyboard や Xib で代替する」というイメージです。UIKit だけで View を構築することを推奨している例もあります。
当然ではありますが、GUI (Storyboard や Xib) で View を構築する方が学習コストや初期実装コストが低いのに対し、UIKit のみで View を構築する方がメンテナンス性・再利用性が高まるのです。参考
- Interface Builderに依存しないiOS開発のススメ
- Storyboard派がコードでUIを実装するためのチュートリアル
- StoryboardやXibは使わない方が良いかも?
- iOSとコードベースレイアウト
まとめ
以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1
- SwiftUI のみで実装する
- UIKit のみで実装する
- Storyboard + UIKit で実装する
- Xib + UIKit で実装する
- Storyboard + Xib + UIKit で実装する
どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。
SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 ↩
- 投稿日:2020-01-04T21:27:33+09:00
iOS アプリ開発で登場する View 実装方法の関係 (SwiftUI、Storyboard、Xib、UIKit)
Swift で iOS アプリを開発するにあたり View の実装方法を調べていたところ、Storyboard、Xib、UIKit といった用語の関係性が全然分からず非常に苦労しました。
これらの全体的な関係性が (自分にとって) 分かりやすくまとまっている記事などが全く見つからなかったので、現時点での自分の理解をまとめます。
※ iOS 開発初心者のため、間違いあればご指摘ください
結論 (ざっくりした分類)
SwiftUI、Storyboard、Xib、UIKit をざっくり分類すると以下の通りです。
分類 要素名 GUI or コード コンポーネント化 概要 SwfitUI SwiftUI コード ◯ iOS 13 以上で使える新しい実装方法 SwfitUI 登場以前 Storyboard GUI × GUI でアプリの View 全体を管理 Xib GUI ◯ GUI で View のコンポーネントを作成 UIKit コード ◯ Swift のコードで View 周辺を扱うフレームワーク 以下でそれぞれ説明していきます。
SwiftUI vs SwiftUI 登場以前
まず大きな分類として、Swift UI と Swift UI 登場以前の 2 種類の実装方法があります。
Xcode の最近のバージョンで新規プロジェクトを作成しようとすると、User Interface の選択肢に SwiftUI と Storyboard が並んでいます。
この選択肢は、SwiftUI を使うか、それ以前の実装方法を使うかを選ぶのとかなり近いです。SwiftUI
2019 年に登場した、iOS 13 以降でのみ利用可能な新しい UI 実装方法です。
コードで UI を実装することになります。
ざっくり言うと、Flutter での UI 実装と似ていると言われています。現時点では細かい UI の再現は苦手な場合もあるようです。
なお、SwiftUI と SwiftUI 登場以前の実装方法 (UIKit) は共存可能とのことです。
参考
SwiftUI 登場以前
さて、ここが一番難しかったのですが、SwiftUI 登場以前の UI の実装方法には以下の 3 つがあります。
- Stobyboard (GUI)
- Xib (GUI)
- UIKit (コード)
これらからどれか 1 つを選んで実装するのかと思いきや、そうではありません。
UIKit 単独で View を実装することも可能ですが、Storyboard や Xib と組み合わせて実装することも多いようです。以下、これら 3 つを簡単に説明していきます。
Storyboard
GUI でアプリの各ページの View や画面遷移 (Segue) を実装するものです。
Storyboard だけで View を全て実装することができます。
View のコンポーネント化は苦手で、View 全体の管理に適しているようです。
View とプログラム (UIViewController) の紐付けは GUI の操作で実施します。Xib
GUI で各ページの View やコンポーネントを作る方法です。
Storyboard が苦手とする、View の再利用に適しています。
Xib で作成した View のコンポーネントは Storyboard やコードから利用可能です。ちなみに、Storyboard や Xib を編集するための Xcode のエディタを Interface Builder と言います。
参考
- StoryboardのBestPracticeについて考える(2) 〜ビューの再利用〜
- iOS 開発で storyboard と xib をうまく使い分けるプラクティス
- Storyboard よりも Xib を使いたい理由
- xibファイルでビューを作成して、ストーリーボードやコードから利用する方法(Swift3編)
UIKit
UIKit 自体は、SwiftUI 登場以前の iOS アプリの View 開発全般を担うフレームワークです。
UIKit は View の構築だけでなく、View とプログラムの紐付け (UIViewController) なども含んでいます。
なので、Storyboard や Xib で View を構築する場合であっても、プログラムとの紐付けのために UIKit を使うことになります。Storyboard や Xib でできることは UIKit で何でもできます。
「UIKit で View を構築するのは大変なので、Storyboard や Xib で代替する」というイメージです。UIKit だけで View を構築することを推奨している例もあります。
当然ではありますが、GUI (Storyboard や Xib) で View を構築する方が学習コストや初期実装コストが低いのに対し、UIKit のみで View を構築する方がメンテナンス性・再利用性が高まるのです。参考
- Interface Builderに依存しないiOS開発のススメ
- Storyboard派がコードでUIを実装するためのチュートリアル
- StoryboardやXibは使わない方が良いかも?
- iOSとコードベースレイアウト
まとめ
以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1
- SwiftUI のみで実装する
- UIKit のみで実装する
- Storyboard + UIKit で実装する
- Xib + UIKit で実装する
- Storyboard + Xib + UIKit で実装する
どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。
SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 ↩
- 投稿日:2020-01-04T21:27:33+09:00
iOS アプリの View 実装方法の関係 (SwiftUI、Storyboard、Xib、UIKit)
Swift で iOS アプリを開発するにあたり View の実装方法を調べていたところ、Storyboard、Xib、UIKit といった用語の関係が全然分からず非常に苦労しました。
これらの全体的な関係が (自分にとって) 分かりやすくまとまっている記事などが全く見つからなかったので、現時点での自分の理解をまとめます。
※ iOS 開発初心者のため、間違いあればご指摘ください
結論 (ざっくりした分類)
SwiftUI、Storyboard、Xib、UIKit をざっくり分類すると以下の通りです。
分類 要素名 GUI or コード コンポーネント化 概要 SwfitUI SwiftUI コード ◯ iOS 13 以上で使える新しい実装方法 SwfitUI 登場以前 Storyboard GUI × GUI でアプリの View 全体を管理 Xib GUI ◯ GUI で View のコンポーネントを作成 UIKit コード ◯ Swift のコードで View 周辺を扱うフレームワーク 以下でそれぞれ説明していきます。
SwiftUI vs SwiftUI 登場以前
まず大きな分類として、Swift UI と Swift UI 登場以前の 2 種類の実装方法があります。
Xcode の最近のバージョンで新規プロジェクトを作成しようとすると、User Interface の選択肢に SwiftUI と Storyboard が並んでいます。
この選択肢は、SwiftUI を使うか、それ以前の実装方法を使うかを選ぶのとかなり近いです。SwiftUI
2019 年に登場した、iOS 13 以降でのみ利用可能な新しい UI 実装方法です。
コードで UI を実装することになります。
ざっくり言うと、Flutter での UI 実装と似ていると言われています。現時点では細かい UI の再現は苦手な場合もあるようです。
なお、SwiftUI と SwiftUI 登場以前の実装方法 (UIKit) は共存可能とのことです。
参考
SwiftUI 登場以前
さて、ここが一番難しかったのですが、SwiftUI 登場以前の UI の実装方法には以下の 3 つがあります。
- Stobyboard (GUI)
- Xib (GUI)
- UIKit (コード)
これらからどれか 1 つを選んで実装するのかと思いきや、そうではありません。
UIKit 単独で View を実装することも可能ですが、Storyboard や Xib と組み合わせて実装することも多いようです。以下、これら 3 つを簡単に説明していきます。
Storyboard
GUI でアプリの各ページの View や画面遷移 (Segue) を実装するものです。
Storyboard だけで View を全て実装することができます。
View のコンポーネント化は苦手で、View 全体の管理に適しているようです。
View とプログラム (UIViewController) の紐付けは GUI の操作で実施します。Xib
GUI で各ページの View やコンポーネントを作る方法です。
Storyboard が苦手とする、View の再利用に適しています。
Xib で作成した View のコンポーネントは Storyboard やコードから利用可能です。ちなみに、Storyboard や Xib を編集するための Xcode のエディタを Interface Builder と言います。
参考
- StoryboardのBestPracticeについて考える(2) 〜ビューの再利用〜
- iOS 開発で storyboard と xib をうまく使い分けるプラクティス
- Storyboard よりも Xib を使いたい理由
- xibファイルでビューを作成して、ストーリーボードやコードから利用する方法(Swift3編)
UIKit
UIKit 自体は、SwiftUI 登場以前の iOS アプリの View 開発全般を担うフレームワークです。
UIKit は View の構築だけでなく、View とプログラムの紐付け (UIViewController) なども含んでいます。
なので、Storyboard や Xib で View を構築する場合であっても、プログラムとの紐付けのために UIKit を使うことになります。Storyboard や Xib でできることは UIKit で何でもできます。
「UIKit で View を構築するのは大変なので、Storyboard や Xib で代替する」というイメージです。UIKit だけで View を構築することを推奨している例もあります。
当然ではありますが、GUI (Storyboard や Xib) で View を構築する方が学習コストや初期実装コストが低いのに対し、UIKit のみで View を構築する方がメンテナンス性・再利用性が高まるのです。参考
- Interface Builderに依存しないiOS開発のススメ
- Storyboard派がコードでUIを実装するためのチュートリアル
- StoryboardやXibは使わない方が良いかも?
- iOSとコードベースレイアウト
まとめ
以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1
- SwiftUI のみで実装する
- UIKit のみで実装する
- Storyboard + UIKit で実装する
- Xib + UIKit で実装する
- Storyboard + Xib + UIKit で実装する
どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。
SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 ↩
- 投稿日:2020-01-04T21:05:53+09:00
SwiftUIでAVFoundationを使ってみた
はじめに
SwiftUIとAVFoundationをどうやったら組み合わせられるのかなっと考えてみました。
AVFoundationをObservableObjectにしたらカメラの画像を表示せずに顔検出とかできるのはないかと考えていました。
ということでまず作ってみました。作る上で必要なファイル一式
NewFile...で下記のファイルを新規作成してプロジェクトに追加してください
種別 ファイル名 概要 SwftUI View CALayerView CALayer用のView Swift File AVFoundationVM AVFoundationをObservableObject化するファイル 作り方
Step1.カメラのアクセス許可
Privacy - Camera Usage Descriptionを追加し利用用途を記載してください。
今回は、「カメラを利用します。」とします。
Step2.AVFoundationをObservableObject化
インスタンス生成時にAVFoundationの初期化処理を行い、
startSession()
endSession()
を呼び出すことでカメラの制御を開始終了します。takePhoto()
を呼び出すと画像をimage
に格納します。
image
は@Published
してありますので格納するとSwiftUI側で画像表示などに使えると思います。AVFoundationVM.swiftimport UIKit import Combine import AVFoundation class AVFoundationVM: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, ObservableObject { ///撮影した画像 @Published var image: UIImage? ///プレビュー用レイヤー var previewLayer:CALayer! ///撮影開始フラグ private var _takePhoto:Bool = false ///セッション private let captureSession = AVCaptureSession() ///撮影デバイス private var capturepDevice:AVCaptureDevice! override init() { super.init() prepareCamera() beginSession() } func takePhoto() { _takePhoto = true } private func prepareCamera() { captureSession.sessionPreset = .photo if let availableDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.first { capturepDevice = availableDevice } } private func beginSession() { do { let captureDeviceInput = try AVCaptureDeviceInput(device: capturepDevice) captureSession.addInput(captureDeviceInput) } catch { print(error.localizedDescription) } let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) self.previewLayer = previewLayer let dataOutput = AVCaptureVideoDataOutput() dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String:kCVPixelFormatType_32BGRA] if captureSession.canAddOutput(dataOutput) { captureSession.addOutput(dataOutput) } captureSession.commitConfiguration() let queue = DispatchQueue(label: "FromF.github.com.AVFoundationSwiftUI.AVFoundation") dataOutput.setSampleBufferDelegate(self, queue: queue) } func startSession() { if captureSession.isRunning { return } captureSession.startRunning() } func endSession() { if !captureSession.isRunning { return } captureSession.stopRunning() } // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if _takePhoto { _takePhoto = false if let image = getImageFromSampleBuffer(buffer: sampleBuffer) { DispatchQueue.main.async { self.image = image } } } } private func getImageFromSampleBuffer (buffer: CMSampleBuffer) -> UIImage? { if let pixelBuffer = CMSampleBufferGetImageBuffer(buffer) { let ciImage = CIImage(cvPixelBuffer: pixelBuffer) let context = CIContext() let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer)) if let image = context.createCGImage(ciImage, from: imageRect) { return UIImage(cgImage: image, scale: UIScreen.main.scale, orientation: .right) } } return nil } }Step3.CALayerをSwiftUIで利用できるようにする
AVFoundationのカメラのライブビュー画像を表示できるように準備します。
CALayerView.swiftimport SwiftUI struct CALayerView: UIViewControllerRepresentable { var caLayer:CALayer func makeUIViewController(context: UIViewControllerRepresentableContext<CALayerView>) -> UIViewController { let viewController = UIViewController() viewController.view.layer.addSublayer(caLayer) caLayer.frame = viewController.view.layer.frame return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CALayerView>) { caLayer.frame = uiViewController.view.layer.frame } }Step4.最後にSwiftUIを作成する
今回は、シャッターボタンを押すと撮影画像を表示するように作りました。
ContentView.swiftimport SwiftUI struct ContentView: View { @ObservedObject private var avFoundationVM = AVFoundationVM() var body: some View { VStack { if avFoundationVM.image == nil { Spacer() ZStack(alignment: .bottom) { CALayerView(caLayer: avFoundationVM.previewLayer) Button(action: { self.avFoundationVM.takePhoto() }) { Image(systemName: "camera.circle.fill") .renderingMode(.original) .resizable() .frame(width: 80, height: 80, alignment: .center) } .padding(.bottom, 100.0) }.onAppear { self.avFoundationVM.startSession() }.onDisappear { self.avFoundationVM.endSession() } Spacer() } else { ZStack(alignment: .topLeading) { VStack { Spacer() Image(uiImage: avFoundationVM.image!) .resizable() .scaledToFill() .aspectRatio(contentMode: .fit) Spacer() } Button(action: { self.avFoundationVM.image = nil }) { Image(systemName: "xmark.circle.fill") .renderingMode(.original) .resizable() .frame(width: 30, height: 30, alignment: .center) .foregroundColor(.white) .background(Color.gray) } .frame(width: 80, height: 80, alignment: .center) } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { Group { ContentView() } } }参考情報
- 投稿日:2020-01-04T15:40:06+09:00
デコレーターをSwift5で実装する
※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。
The Decorator(デコレータ)
0. デコレータの意義
デコレータは、あるクラスなどに元は無かった振る舞いを追加するデザインパターンである。元のクラスのコードをいじることなく振る舞いを柔軟に変えることができる。
また継承を行うのとは違って、プログラム実行時に動的にデコレータを使用し機能拡張することもできる。
注意点としては、元のクラスと全く関連性がないような機能をデコレータで付け加えるのはよろしくないという事である。単一のクラスは単一の機能・責務だけを持つべきという「単一責務の原則」に反し、分かりづらく保守しにくいコードになってしまう。
※単一責務の原則については下記などを参照
https://qiita.com/decoy0318/items/f201b725e91a4a1bb4cfSwiftではextensionを用いるか、デコレータクラスを新たに作る方法がある。
1. extensionを使う場合
extensionを使うと元のクラスにそのまま新しい機能を追加できるため非常に直感的で使いやすくなっている。
UIColorExtension.swiftimport UIKit public extension UIColor { //HTMLコード(#1411A4など)からUIColorインスタンスを作るイニシャライザを新設する convenience init(hex: UInt32) { let divisor = CGFloat(255) let red = CGFloat((hex & 0xFF0000) >> 16) / divisor let green = CGFloat((hex & 0x00FF00) >> 8) / divisor let blue = CGFloat(hex & 0x0000FF) / divisor self.init(red: red, green: green, blue: blue, alpha: 1) } }ただしextensionには制約があり、ストアドプロパティを新設するor既存のストアドプロパティの振る舞いを変更することはできない。
※ストアドプロパティについては下記参照
https://qiita.com/akeome/items/2197a635ac616ab2f8e2その場合には、下記のようにデコレータクラスを使うことになる。
2.デコレータクラスを使う場合
下記はUILabelにデコレータを使った例。
デコレータクラス自体が、目的のクラス(ここではUILabel)を継承する。
UILalbelDecorator.swiftimport UIKit public class BorderedLabelDecorator: UILabel {プロパティとしてもUILabelを保持する。
UILalbelDecorator.swiftfileprivate let wrappedLabel: UILabelUILabelの変数の振る舞いを変更していく。
UILalbelDecorator.swiftpublic required init(label: UILabel, cornerRadius: CGFloat = 3.0, borderWidth: CGFloat = 1.0, borderColor: UIColor = .black) { self.wrappedLabel = label super.init(frame: label.frame) self.wrappedLabel.layer.cornerRadius = cornerRadius self.wrappedLabel.layer.borderWidth = borderWidth self.wrappedLabel.layer.borderColor = borderColor.cgColor self.wrappedLabel.clipsToBounds = true } public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } //使用予定のあるUILabelの変数をオーバーライドし、wrappedLabelとBorderedLabelDecoratorの変数がリンクするようにする public override var textAlignment: NSTextAlignment { get { return self.wrappedLabel.textAlignment } set { self.wrappedLabel.textAlignment = newValue } } public override var backgroundColor: UIColor? { get { return self.wrappedLabel.backgroundColor } set { self.wrappedLabel.backgroundColor = newValue } } public override var textColor: UIColor? { get { return self.wrappedLabel.textColor } set { self.wrappedLabel.textColor = newValue } } public override var layer: CALayer { return self.wrappedLabel.layer } //元のストアドプロパティの振る舞いを変更する(文字をセットした時に顔文字を付け加える) public override var text: String? { get { return self.wrappedLabel.text } set { var str = "?" if let newVal = newValue { str += newVal + str } self.wrappedLabel.text = str } } }https://github.com/Satoru-PriChan/DecoratorUIColorUILabelDemo
参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ
- 投稿日:2020-01-04T14:54:47+09:00
AppStoreに掲載するスクリーンショットに「TestFlight」の文字があると審査でリジェクトになるのでご注意を..
事象
モバイルアプリのリリース作業で、iOSアプリを審査に出してドキドキ待っていたら、審査出してから3.5時間でAppleからメールが来ました。年始早々なのに対応が早くありがたいです。
App Store Connectにログインするとステータスが「メタデータ却下済み」になってました。ドキっ…
メタデータ却下済みとは?
ステータスの見方はこちらのAppStoreConnectヘルプページに詳しい説明がありますが、メタデータ(App の名前、概要、キーワード、スクリーンショットなど、App Store Connect に入力する App 情報とプラットフォームのバージョン情報)が不正だよという意味なのでビルドし直しは不要で、登録情報修正して返信することでまた審査再開するというステータスです。
上記画面で iOS での未解決の問題:1 のリンクをクリックもしくはアクティビティタブをクリックするとApple Reviewチームからの詳細コメント文が記載されてまして、その中に以下のコメントが。
Guideline 2.3.3 - Performance - Accurate Metadata
We noticed that your screenshots do not sufficiently reflect your app in use.
Specifically, your 12.9-inch iPad Pro, 12.9-inch iPad Pro (3rd Generation), and 6.5-inch iPhone screenshots display "Back to TestFlight" in the status bar.
Next Steps
To resolve this issue, please revise your screenshots to ensure that they accurately reflect the app in use on the supported devices. For example, a gaming app should feature screenshots that capture actual gameplay from within the app. Marketing or promotional materials that do not reflect the UI of the app are not appropriate for screenshots.
The iPhone screenshots should reflect use on iPhone devices.
The iPad screenshots should reflect use on iPad devices.
App Store screenshots should accurately communicate your app’s value and functionality. Use text and overlay images to highlight your app’s user experience, not obscure it. Make sure app UI and product images match the corresponding device type in App Store Connect. This helps users understand your app and makes for a positive App Store experience.
Resources
For resources on creating great screenshots for the App Store, you may want to review the App Store Product Page information available on the Apple Developer website.
Please ensure you have made any screenshot modifications using Media Manager. You should confirm your app looks and behaves identically in all languages and on all supported devices. Learn more about uploading app previews and screenshots in App Store Connect Help.Google翻訳だと以下です。
ガイドライン2.3.3-パフォーマンス-正確なメタデータ
スクリーンショットが使用中のアプリを十分に反映していないことがわかりました。
具体的には、12.9インチiPad Pro、12.9インチiPad Pro(第3世代)、および6.5インチiPhoneのスクリーンショットのステータスバーに「TestFlightに戻る」と表示されます。
次のステップ
この問題を解決するには、スクリーンショットを修正して、サポートされているデバイスで使用中のアプリが正確に反映されるようにしてください。たとえば、ゲームアプリには、アプリ内から実際のゲームプレイをキャプチャしたスクリーンショットを含める必要があります。アプリのUIを反映していないマーケティング資料や販促資料は、スクリーンショットには適していません。
iPhoneスクリーンショットは、iPhoneデバイスでの使用を反映する必要があります。
iPadのスクリーンショットは、iPadデバイスでの使用を反映する必要があります。
App Storeのスクリーンショットは、アプリの価値と機能を正確に伝える必要があります。テキストとオーバーレイ画像を使用して、アプリのユーザーエクスペリエンスを目立たせずに強調します。アプリのUIと製品の画像が、App Store Connectの対応するデバイスタイプと一致していることを確認してください。これにより、ユーザーはアプリを理解し、App Storeの利便性を高めることができます。
資源
App Storeの優れたスクリーンショットを作成するためのリソースについては、Apple Developer Webサイトで入手可能なApp Store製品ページ情報を確認することをお勧めします。
Media Managerを使用してスクリーンショットの変更を行ったことを確認してください。アプリの外観と動作が、すべての言語とサポートされているすべてのデバイスで同一であることを確認する必要があります。 App Store Connectヘルプで、アプリのプレビューとスクリーンショットのアップロードの詳細をご覧ください。噂には聞いてて知ってたはずなのに忘れてました…
原因
私はAppStoreに掲載するためのApp プレビューとスクリーンショットに追加する画像ファイルをTestFlightでインストールしたiOSデバイス実機のスクリーンショットを元にして登録してたのですが。
TestFlightからインストールした同じアプリでも、TestFlightからアプリを開いた場合とホーム画面上のアプリアイコンをタップして開いた場合とで、左上の表示が異なります。
TestFlightでインストールしたアプリは、ホーム画面のアプリ名の先頭にオレンジの●が表示されるようです。
そうです。この左上の「TestFlight」という文字がリジェクトの原因でした‥
これだけ?と思うかもしれませんが、公式Appleが出しているApp Reviewにも「不正確なスクリーンショットはリジェクトの原因ですよ」と書いてあります。
test
とかβ版
などの表示があるとリジェクトになる可能性が高いみたいです。。
対処
スクリーンショットを修正して差し替え、問題解決センター画面からApp Reviewチームへの返信を英語で書いて送信した所解消しました。 (ホッ…)
私のような方はいないかもしれませんがもし同じように不慣れな方いらっしゃいましたらご注意ください。
- 投稿日:2020-01-04T13:13:26+09:00
iOSのショートカットでWebサイトの二段階認証コード入力の手間を軽減する
はじめに
Webサイトの二段階認証コード(以下、認証コード)を入力するのは、以下のステップを踏む必要があり、なかなか手間がかかります。
- Webサイトから二段階認証のアプリ(以下、認証アプリ)に切り替えて認証コードを取得する。
- その後、Webサイトに戻って、認証コードをフォームに入力する。
上記の手間をiOSのショートカットを使って自動化することで軽減する方法について紹介したいと思います。
前提
- iOSのショートカットを使用するため、iOS12以上が必要になります。
- ただし、本記事はiOS13.3で検証を行なっています。
- 認証アプリのインストールが必要です。本記事のショートカットでは、以下のアプリを例に説明しています。
- Google Authenticator
- Microsoft Authenticator
- Authy
ショートカットの雛形
Webサイトの共有ボタンからショートカットを実行したいため、作成するショートカットはSafariのWebページを受け入れる設定を行います。
このWebページを受け入れる設定のショートカットを作成するにあたり、ショートカットアプリのギャラリーにあるJavaScriptショートカットのサンプルから複製する方法が手っ取り早いです。
例えば、JavaScriptショートカットの
Webページを編集
のショートカットを追加します。その後、このショートカットを編集し、JavaScriptを実行する部分のコードを一旦completion()
のみにします。個別のWebサイトに対するショートカット例
個別のWebサイトに対して認証コードを入力するケースについて、Amazonを例に説明します。
Amazonの認証コードの入力画面では、以下の要素が宣言されています。
auth-mfa-otpcode
のID属性を持つインプット要素(認証コードの入力フォーム)auth-signin-button
のID属性を持つボタン要素(サインインボタン)上記の要素に対して、ショートカットからJavaScriptを実行し、インプット要素に認証コードを設定した後にサインインボタンをクリックすると手間を軽減できそうです。
ショートカットの主な手順
- SafariのWebページを受け入れる
- ここは、テンプレートの雛形で作成済みです。
- スクリプティングの
アプリを開く
を追加する。
- Amazonの認証コードを発行する認証アプリ(ここでは、Google Authenticatorアプリ)を起動します。
- 認証アプリ起動後に、認証コードをクリップボードにコピーします。
- スクリプティングの
待機
を追加します。JavaScriptを実行
のコードで以下の処理を行います。
- クリップボードにコピーした認証コードをWebサイトのフォームに設定する。
auth-mfa-otpcode
のID属性を持つインプット要素を取得し、インプット要素の値に認証コードを設定します。- サインインボタンをクリックする。
auth-signin-button
のID属性を持つ要素を取得し、click()
を呼び出します。- JavaScriptの実行を完了する。
completion(true)
を呼び出します。JavaScriptの実行コード
// 入力フォームに認証コードを設定 document.getElementById("auth-mfa-otpcode").value = '<クリップボードの変数>'; // サインインボタンをクリック document.getElementById("auth-signin-button").click() // JavaScriptの実行を完了 completion(true)作成したショートカット
汎用的なWebサイトに対するショートカット例
個別のWebサイトに対してショートカットを作成すると自動化が最適化しやすいですが、二段階認証したいサイトが多いとショートカットの作成に時間がかかります。
そこで、汎用的なWebサイトに対しても認証コードの入力の手間を軽減する方法について説明します。
汎用版 ver1
ショートカットのメニューから任意の認証アプリの起動・認証コードをコピーし、認証コードの設定とボタンのクリックは手動で行う例です。
ショートカットの主な手順
- SafariのWebページを受け入れる
- ここは、テンプレートの雛形で作成済みです。
- スクリプティングの
メニューから選択
を追加します。
- メニューに複数の認証アプリを追加します。(この部分は個人の環境によって任意のアプリを指定します。)
- Google Authenticator
- Microsoft Authenticator
- Authy
- それぞれの認証アプリの項目に対して、スクリプティングの
アプリを開く
を追加し、アプリの設定を行います。- スクリプティングの
待機
を追加します。JavaScriptを実行
のコードで以下の処理を行います。
- JavaScriptの実行を完了する。
completion(true)
を呼び出します。JavaScriptの実行コード
特に認証コードの設定とボタンのクリックはショートカット上では行わないため、単純に完了のコールバック関数を呼び出す処理のみになります。
// JavaScriptの実行を完了 completion(true);認証コードの取得以降では、手動で認証コードのインプット要素にクリップボード上の認証コードをペーストし、その後、ボタンを押下します。
作成したショートカット
汎用版 ver2
汎用版 ver1では、手動で認証コードをインプット要素にペーストする必要があり、若干煩わしいです。
ただし、認証コードのインプット要素は、各サイトによってID属性やクラス属性、type属性の値が統一されていないため、特定がしづらいです。苦肉の策として、ここではインプット要素にフォーカスを当てた時に、フォーカスされた要素に認証コードを設定する方法について説明します。
ショートカットの主な手順
- SafariのWebページを受け入れる
- ここは、テンプレートの雛形で作成済みです。
- スクリプティングの
メニューから選択
を追加します。
- メニューに複数の認証アプリを追加します。(この部分は個人の環境によって任意のアプリを指定いsます。)
- Google Authenticator
- Microsoft Authenticator
- Authy
- それぞれの認証アプリの項目に対して、スクリプティングの
アプリを開く
を追加し、アプリの設定を行います。- スクリプティングの
待機
を追加します。JavaScriptを実行
のコードで以下の処理を行います。
- 入力要素を取得し、focusイベントを追加する。
- focusイベント時に、ターゲット要素にクリップボード上の認証コードを設定する
- JavaScriptの実行を完了する。
completion(true)
を呼び出します。JavaScriptの実行コード
入力要素に対して、focusイベントを追加し、focusイベント時にターゲット要素に対して認証コードを設定します。ボタンの要素のクリックはJavaScriptでは行いません。
※ 認証コード以外のインプット要素にフォーカスした場合にも認証コードが設定されてしまうので、利用には注意してください。// 入力要素に対して、focusイベントを追加する // focusイベント時にターゲット要素にクリップボード上の認証コードを設定する。 var elms = document.querySelectorAll("input"); elms.forEach((elm) => { elm.addEventListener('focus', (event) => { event.target.value = '<クリップボードの変数>' }); }); // JavaScriptの実行を完了 completion(true);認証コードの取得以降では、手動で認証コードのインプット要素にフォーカスを当てることでクリップボード上の認証コードを設定し、その後、ボタンを押下します。
作成したショートカット
さいごに
iOSのショートカットを使うことで、認証コード入力の手間を少しは軽減できるようにはできたかと思います。さらに手間を軽減するには、認証アプリを起動して認証コードを取得するのではなく、ローカル あるいは リモートサーバ上(SSH経由)で認証コードを生成・取得すると良いのではと考えています。
参考
- 投稿日:2020-01-04T02:23:00+09:00
bazelでcarthageで入れたframeworkをimportする
Carthageのframeworkの直下にBUILDを置くと便利そう。
Carthage/Build/iOS/BUILDpackage(default_visibility = ["//visibility:public"]) load( "@build_bazel_rules_apple//apple:apple.bzl", "apple_dynamic_framework_import", "apple_static_framework_import", ) apple_dynamic_framework_import( name = "Closures", framework_imports = glob(["Closures.framework/**"]), )こんな感じ。visibilityをpublicにしないとアプリのBUILDから参照できないので注意
あとはアプリ側のBUILDはこんな感じswift_library( name = "Sources", srcs = glob(["*.swift"]), data = [ "Base.lproj/LaunchScreen.storyboard", "Base.lproj/Main.storyboard", ], deps = [ "//Carthage/Build/iOS:Closures" ] )