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); } ) ) ); } }
参考
GitのリモートURLをクリップボードにコピーする(macOS Only)
リモートリポジトリをcloneする際、既にある別のローカルリポジトリからリモートURLをコピー&ペーストして末尾を少し書き換えてcloneするということをよくやるのですが、
git remote -v
でURL表示- マウスで選択して
⌘+c
でコピー
とやっていたので地味に面倒でした。
ということで、コマンド一発でコピー出来るスクリプトをRubyで書いてみました。 originブランチのリモートURLをクリップボードにコピーします。
即席&Rubyほぼ初心者なのでかなり適当です。
また、内部でmacOSのpbcopy
コマンドを使用しているため、macOSでのみ動作します。
#!/usr/bin/env ruby git_command = 'git remote -v' git_result = `#{git_command}` if git_result.empty? puts "Failed to run command: '#{git_command}'" exit end url = git_result.split("\n") .select {|line| line.start_with?("origin")}.first .split(" ")[1] `printf #{url} | pbcopy` puts "Copied '#{url}'"
初めて知りましたが、String.split(" ")
(半角スペース1文字)で空白文字を全部分割してくれるんですね。
なんという便利機能...
fastlaneでAdHoc配布用ipaの作成
環境
laneの定義
gym
アクション(もしくはbuild_ios_app
アクション)でビルドやパッケージングに関する処理が出来ます。lane名はここではadhoc
とします。
また、署名周りの設定はあらかじめXcode上で完了させておきます。確認時はAutomatic Signingを使用しました。
default_platform(:ios) platform :ios do ... desc 'Generate IPA file for AdHoc' lane :adhoc do gym( project: 'XXX.xcodeproj', configuration: 'Debug', scheme: 'XXX', clean: true, include_bitcode: false, output_directory: './build', output_name: 'XXX.ipa', export_method: 'ad-hoc' ) end ... end
- project: プロジェクトファイル(.xcodeproj)のパスを指定。
- configuration: Build Configuration(Debug/Release)を指定。
- scheme: Scheme名称を指定。必ずShared指定になっていることを確認すること。
- clean: ビルド前にクリーンするかどうか。念の為クリーンするようにしておく。
- include_bitcode: bitcodeを含めるかどうか。AdHoc配布であれば恐らく不要だと思うのでここでは含めない。
- output_directory: ipaの出力先ディレクトリ。
- output_name: ipaファイル名。
- export_method: AdHoc配布なので'ad-hoc'を指定。
もしworkspace(.xcworkspace)を使っている場合は引数のproject
をworkspace
に変更すれば良いでしょう。
gym( workspace: XXX.xcworkspace`, ... )
実行
$ fastlane adhoc
以上でプロジェクトルート直下にbuild
ディレクトリが作成され、その中にipaファイル(XXX.ipa
)が作成されるかと思います。