Xcode10のちょっとした変更点: StoryboardからOutlet/Actionを作成する際のデフォルトConnection
Xcode10でStoryboard上のUIButtonからコード上にConnection(Outlet/Action)を作成する際、挿入位置によってデフォルトで選択済のConnectionが変わるようになっていました。(以前は"Outlet"固定だったかと思います)
具体的には、コードのViewControllerクラスに定義しているメソッドの下に挿入すると、デフォルトのConnectionが"Action"に変わるようです。
- メソッドの上に挿入しようとする場合、Outletがデフォルトになる
- メソッドの下に挿入しようとする場合、Actionがデフォルトになる
メソッド以外の場合は以前までと同様に"Outlet"がデフォルトになるようです。 以下の要素の下に挿入しようとする場合でも、"Outlet"がデフォルトになることを確認しました。
- Stored Properties
- Computed Properties
- Nested Types
- Class
- Struct
- Enum
ちょっとしたことではありますが、Actionを作ろうとして間違ってOutletを作ってしまったり、いちいちActionに切り替える等の面倒だったことが改善される良い変更だと思います。
Androidエミュレータで動作させるSystem Imageの違い
Android開発時にインストールするエミュレータのSystem Imageの違い、特に「Google APIs」と「Google Play」の違いがよく分かっていなかったのでメモしておきます。
Google Play System Image
- Google Play Storeがインストールされており、実機と同じようにアプリがStoreからインストール出来る
- root化されていない
- イメージを動作させる仮想デバイスに制限がある。電話端末だと以下が対応しているようです。(Android Studio 3.2.1時点)
実機に近い環境だと思うので、基本的にはこちらを選択しておけば良さそうですね。
Google APIs System Image
- Google Play Storeは使用不可
- root化されている
- 仮想デバイスの制限が無い
CPU命令セットの違い
参考
Android(Xamarin) 文字列の横幅取得方法
Androidにおける文字列の横幅(dp)を取得する方法のメモです。 親Layoutの横幅と比較して、1行に何文字表示出来るか等の計算が出来たりします。
現在Xamarinで開発中のため、コードもXamarin(C#)になってしまっていますが、Android Nativeでも書き方はそう変わらないかと思います。
文字列のサイズ取得
TextViewクラスから取得出来るPaintオブジェクトを使い、MeasureText()を呼び出します。
using Android.App; using Android.Content.Res; using Android.Util; using Android.Widget; namespace XXX { public class StringWidthCalculator { public double CalculateStringWidth(string str, double fontSize) { var textView = new TextView(Application.Context); textView.SetTextSize(ComplexUnitType.Sp, (float)fontSize); var widthOfPixel = textView.Paint.MeasureText(str); return widthOfPixel / Resources.System.DisplayMetrics.Density; } } }
TextViewにフォントサイズを設定しておくことでフォントサイズに応じた文字列のサイズが取得出来るようです。
ComplexUnitType.Sp
としているため、ユーザのフォントサイズ設定に応じて横幅が変動します。
また、MeasureText()で返却される横幅はpixel単位のため、この関数から返却する際にdpに変換しています。
使い方
var str = "abcde"; double fontSize = 13; var calculator = new StringWidthCalculator(); var width = calculator.CalculateStringWidth(str, fontSize);
サンプルコード
iOS 文字列のサイズ取得方法
iOSにおける文字列のサイズ取得方法のメモです。 Viewの横幅と比較して、1行に何文字表示出来るか等の計算が出来たりします。
文字列のサイズ取得
NSStringクラスにサイズが取得出来るメソッドがあるので、こちらを使います。
UIFontを渡してあれば、フォントサイズに応じた文字列のサイズが取得出来ます。
// 今回はStringクラスのExtensionとして実装 import UIKit public extension String { public func size(with font: UIFont) -> CGSize { let attributes = [NSAttributedStringKey.font : font] return (self as NSString).size(withAttributes: attributes) } }
使い方
let font = UIFont.systemFont(ofSize: 13) let size = "abcde".size(with: font) let width = size.width let height = size.height
戻り値はCGSizeになっていますが、こちらは1行に全文字を並べた場合のサイズを返すようです。つまり、文字数に応じてwidthだけが伸びていき、heightは常に1文字分の高さを返します。
- フォントサイズ13
- フォントサイズ20
- フォントサイズ20 + 長い文字列
iOS 連絡先一覧の取得
Contacts Frameworkを使用し、標準の連絡先アプリに登録されている連絡先一覧を取得する方法についてまとめてみます。
サンプルコードは以下に置いています。
https://github.com/kurozu10344/ContactsSample
検証環境
連絡先一覧の取得
利用目的の記述(Info.plist)
まず、このFrameworkはユーザのプライバシーなデータにアクセスするため、ユーザに対して連絡先へアクセスする目的を提示する必要があります。この設定をターゲットのInfo.plistに追記します。
KeyはPrivacy - Contacts Usage Description
(Raw Keyの場合はNSContactsUsageDescription
)を、TypeはString
を選択し、Valueにユーザに提示する利用目的を記述します。
ここで設定した文言が、後ほど説明するアクセス許可要求時の確認アラートに表示されます。
この設定を行わずにContacts FrameworkのAPIを呼び出そうとした場合、以下のようなエラーメッセージを表示してアプリが落ちます。よって設定は必須です。
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSContactsUsageDescription key with a string value explaining to the user how the app uses this data.
アクセス許可の確認・要求
ここからコーディングに入ります。
まず、実際に連絡先を取得する前に、ユーザに連絡先アクセスの許可を得ているかを確認し、まだの場合はアクセス許可を求める必要があります。
let status = CNContactStore.authorizationStatus(for: .contacts) switch status { case .notDetermined: // 初回アクセス時 // アクセス許可要求 CNContactStore().requestAccess(for: .contacts) { (granted, error) in if granted { // アクセス許可された } else { // アクセス拒否された } } case .authorized: // アクセス許可済 case .restricted: // ペアレンタルコントロール等の機能制限により利用不可 case .denied: // アクセス拒否済 }
CNContactStore.authorizationStatus(for:)
を呼び出し、許可状態を確認する。- 許可状態が
CNAuthorizationStatus.notDetermined
の場合、CNContactStore.requestAccess(for:completionHandler:)
を呼び出し、アクセス許可を要求する。 - ↑で渡したcompletionHandlerの第一引数(ソース中では
granted
)がtrueの場合、連絡先へのアクセスが可能になる。
CNContactStore.requestAccess(for:completionHandler:)
を呼び出した際に以下のようなアラートが表示され、Info.plistに記述した利用目的が表示されます。
取得処理
アクセスが許可された、あるいは許可済の場合、ようやく連絡先の取得を開始します。
private func loadContacts() { DispatchQueue.global().async { let store = CNContactStore() let keys: [CNKeyDescriptor] = [ CNContactFamilyNameKey as CNKeyDescriptor, CNContactGivenNameKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] let fetchRequest = CNContactFetchRequest(keysToFetch: keys) var contacts: [CNContact] = [] try? store.enumerateContacts(with: fetchRequest) { contact, cursor in contacts.append(contact) } self.contacts = contacts DispatchQueue.main.async { // Viewに表示、等 } } }
- self.contactsは
[CNContact]
型のプロパティです。 連絡先取得にはI/O操作が含まれますが、同期的に実行されるため、バックグラウンドでの処理が推奨されています。
Because the contact store methods are synchronous, it is recommended that you use them on background threads.
keysには連絡先内の必要な項目を列挙します。ここに指定していない項目を取得しようとした場合(例えば
CNContactEmailAddressKey
を指定せずにcontact.emailAddresses
を呼び出した場合)、アプリがクラッシュします。
サンプルソースではCNContactStore.enumerateContacts(with:usingBlock:)
を呼び出し、クロージャの引数に渡された連絡先(contact: CNContact)をリストに詰め込んでいます。
あとはこの連絡先から各種情報を取得して処理していけば良いかと思います。
フルネームの取得
フルネームの取得はCNContact
を直接使わず、CNContactFormatter.string(from:style:)を使用します。(まぁ日本人ならfamilyName
とgivenName
を組み合わせるだけで良いのかもしれませんが。。。)
let fullName = CNContactFormatter.string(from: contact, style: .fullName)
style: .fullName
の箇所に取得したいデータ形式(CNContactFormatterStyle)を指定します。2018年7月現在はフルネームとフルネームのふりがなが指定可能です。
またこの場合、fetchRequestに指定するkeyに以下を含める必要があります。
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
自分のカードは判別不可
iOSでは、特定の連絡先を自分の情報として登録が出来、連絡先アプリに自分のカード(My Card)として表示することが出来ますが、サードパーティアプリからはどの連絡先が自分のカードであるかは判別出来ないようです。 CNContactStore.unifiedMeContactWithKeys(toFetch:)というメソッドがありますが、こちらはmacOS専用のAPIのため、iOSからは使用出来ません。
ファイル内の文字列を置換して上書き保存
便利でたまに使いたくなるのですが、すぐ忘れてしまうので備忘録です。 Perlのワンライナーを使用します。
環境
OS version
Perl version
$ perl -v This is perl 5, version 26, subversion 2 (v5.26.2) built for darwin-thread-multi-2level ...
単一ファイルの置換
$ perl -p -i -e 's/XXX/YYY/g' FilePath
FilePathが示すファイル中の'XXX'を'YYY'に置換する。
各オプションについては以下:
-p
入力ファイルの各行を、後述する-e
オプションのスクリプトで処理して出力する。このオプションを追加することでsed
コマンドのように扱える。
-i
入力ファイルに処理結果を上書きする。間違えた処理を上書きしてしまうと戻せないため、元ファイルはバックアップ推奨。
-e
実行するスクリプトを指定する。
複数ファイルの一括置換
単一ファイルの置換をfindコマンドで複数ファイルに適用するだけ。
$ find . -type f -exec perl -p -i -e 's/XXX/YYY/g' {} \;