TestFlightで「輸出コンプライアンスがありません」を表示されないようにする方法
TestFlightにアプリをアップロードした際に表示されるこの質問、fastlaneでアップロードする際にも表示され、自動でアプリが配信されなくなってしまうのですが、予めプロジェクト設定を追加しておくことで表示されないようにすることが可能です。
設定方法
Info.plist
から ITSAppUsesNonExemptEncryption
(表示名: App Uses Non-Exempt Encrption
) を NO
に設定します。
この設定により、暗号化を使用していない、もしくは免除の対象になる暗号化のみを使用していることを示します。
(HTTPSや、OSに組み込まれた標準の暗号化については免除の対象になるみたいです)
免除の対象にならないような独自の暗号化を含む場合は「YES」を設定し、輸出コンプライアンス書類を提出してAppleから受け取ったキーを ITSEncryptionExportComplianceCode
に設定する必要があるようです。
参考
SwiftUIで共有メニューを表示する
アプリのコンテンツをメールやSNS等の外部サービスに渡す共有メニューをSwiftUIで表示する方法です。
共有メニュー表示用Viewの定義
UIKitではUIActivityViewControllerを使用して表示するこの共有メニューですが、現状SwiftUIで特に専用のViewがある訳ではないようです。 そのため、UIViewControllerRepresentableプロトコルに準拠させる形でUIActivityViewControllerをSwiftUI上に表示出来るようにします。
struct ActivityView: UIViewControllerRepresentable { let activityItems: [Any] let applicationActivities: [UIActivity]? func makeUIViewController( context: UIViewControllerRepresentableContext<ActivityView> ) -> UIActivityViewController { return UIActivityViewController( activityItems: activityItems, applicationActivities: applicationActivities ) } func updateUIViewController( _ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView> ) { // Nothing to do } }
共有メニュー表示
表示の際はsheet()
を使用し、先ほど作成したUIViewControllerRepresentableプロトコルに準拠したViewを返すようにすれば共有メニューが表示されます。
@State private var showActivityView: Bool = false var body: some View { ... Button(action: { self.showActivityView = true }) { Image(systemName: "square.and.arrow.up") } .sheet(isPresented: self.$showActivityView) { ActivityView( activityItems: ["abc"], applicationActivities: nil ) } ... }
参考
iOSアプリでSystem Imageを使用する(iOS13以降)
iOS13ではSystem ImageとしてSF Symbols
というものが追加され、UIKitのUIImage
やSwiftUIのImage
から使用出来るようになりました。
使用方法
それぞれ、以下のようにして名前を指定して表示します。
- UIKit(UIImage)
UIImage(systemName: "xxx")
- SwiftUI(Image)
Image(systemName: "xxx")
名前の確認方法
ここに指定する名前は以下のリンクからSF Symbols
アプリをダウンロードし、Macにインストールする事で確認出来ます。
Apple Design Resources - Apple Developer
SF Symbols
アプリに表示される各アイコンの下にある名前を引数に指定する事で、そのアイコンを使用する事が出来ます。
例えば以下のアイコンの場合、
SwiftUIでは以下のような指定になります。
Image(systemName: "square.and.arrow.up")
参考
UINavigationBarの透明化
まずは透明化/解除のExtensionを作成
public extension UINavigationBar { /// ナビゲーションバーを透明化する func enableTransparency() { setBackgroundImage(UIImage(), for: .default) shadowImage = UIImage() } /// ナビゲーションバーを透明化を解除する func disableTransparency() { setBackgroundImage(nil, for: .default) shadowImage = nil } }
↑をUIViewControllerのviewWillAppear/viewWillDisappearで呼び出し
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.navigationBar.enableTransparency() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.navigationBar.disableTransparency() }
256bit乱数を生成する方法
256bitに限った話ではないのですが、大きめの乱数を生成する方法です。
乱数の生成は以下のように様々な方法がありますが、
- RandomNumberGenerator
- arc4random()
- arc4random_uniform()
- rand()
- random()
いずれにしても最大で64bitまでしか生成出来ないようです。
これ以上の長さの乱数を生成する場合、Security FrameworkのSecRandomCopyBytes()
を使用します。
- 生成
func generate256bitRandom() -> Data? { let count = 32 // 32byte = 256bit var data = Data(count: count) let status = data.withUnsafeMutableBytes { body in SecRandomCopyBytes(kSecRandomDefault, count, body.baseAddress!) } if status == errSecSuccess { return data } return nil }
- 呼び出し
print(generate256bitRandom()!.base64EncodedString()) // "oyICfMgwGXUVeML+Lmhb5ixnAB1io+mNI4BlxNYXVaA=" (呼び出し毎に変わる)
let count = 32
の部分を調整してあげれば、128bitでも512bitでも生成出来ると思います。
UITextViewのパディングを削除する
UITextViewにはデフォルトで若干のパディングが含まれています。このままだとレイアウトに支障をきたす場合もあるので、パディングを削除する方法を記載します。
コードで削除する
以下のようなUITextViewを定義したとして、
@IBOutlet weak var textView: UITextView!
viewDidLoad()
等のタイミングで以下のコードを実行します。
textView.textContainerInset = UIEdgeInsets.zero textView.textContainer.lineFragmentPadding = 0
Storyboardで削除する
コードを使わず、StoryboardのUser defined runtime attributesを使ってパディングを削除することも出来ます。User defined runtime attributesについての詳細は省きますが、簡単に言うとInterface Builder(のAttributes inspector)で設定出来ないようなViewのプロパティを設定出来る機能です。
以下のように2つのプロパティを設定します。
- textContainer.lineFragmentPadding
- textContainerInset
サンプルコード
Flutterで画面遷移
Flutterにおける画面遷移の基本です。 以下の2通りの遷移方法があるようです。
- 直接画面を生成して遷移
- ルーティングを定義して名前で遷移
ここで紹介する方法は、iOS的にはNavigationスタイル(Push/Pop)の画面遷移になります。
概要
Navigator
クラスを使用します。
このクラスはRoute
オブジェクトをスタックとして管理するものです。
Flutterでは、一般的に画面だとかページだとか言われるものをRoute
と呼ぶそうです。
Navigator.push
もしくはNavigator.pushNamed
関数により遷移し、Navigator.pop
関数で前画面に戻ります。
画面の定義
まずは遷移用の画面を定義します。
遷移前の画面(FirstScreen)にRaisedButton
を2つ置き、タップ時にそれぞれのパターンで遷移をします。
遷移後の画面(SecondScreen)にはRaisedButton
を1つ置き、タップ時に前画面(FirstScreen)に戻ります。
class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('First Screen'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text('Navigate directly'), onPressed: () { // 直接画面を生成して遷移 } ), RaisedButton( child: Text('Navigate with named route'), onPressed: () { // ルーティングを定義して名前で遷移 } ), ], ) ) ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Second Screen'), ), body: Center( child: RaisedButton( child: Text('Go back!'), onPressed: () { // 前画面に戻る } ) ) ); } }
直接画面を生成して遷移
Navigator.push
関数を使用し、Route
を指定して遷移します。
Route
にはMaterialPageRoute
を指定しておけば、自動でプラットフォーム固有のトランジションアニメーションを実現してくれます。
(むしろ他のRouteをどうやって作るのかはまだ分かってません)
RaisedButton( child: Text('Navigate directly'), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => SecondScreen()) ); } ),
ルーティングを定義して名前で遷移
MaterialApp
の初期化時にルーティングを定義し、
Navigator.pushNamed
関数を使用して遷移します。
- ルーティング定義
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( ... // 初回起動画面を指定 initialRoute: '/', // ルーティングの定義 routes: { '/': (context) => FirstScreen(), '/second': (context) => SecondScreen(), }, ... ); } }
- 遷移処理
RaisedButton( child: Text('Navigate with named route'), onPressed: () { // ルーティングで定義した名前を指定して遷移 Navigator.pushNamed(context, "/second"); } ),
前画面に戻る
Navigator.pop
関数を使用します。
RaisedButton( child: Text('Go back!'), onPressed: () { Navigator.pop(context); } )
ちなみに、画面のWidgetにScaffold
を使用している場合はAppBarの左側に自動で戻るボタンが表示されるので、そちらから戻ることも出来ます。
ソース全体
- main.dart
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), initialRoute: '/', routes: { '/': (context) => FirstScreen(), '/second': (context) => SecondScreen(), }, ); } } class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('First Screen'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text('Navigate directly'), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => SecondScreen()) ); } ), RaisedButton( child: Text('Navigate with named route'), onPressed: () { Navigator.pushNamed(context, "/second"); } ), ], ) ) ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Second Screen'), ), body: Center( child: RaisedButton( child: Text('Go back!'), onPressed: () { Navigator.pop(context); } ) ) ); } }