macOSの環境設定のようなレイアウトはNSGridViewで実装する

macOSの環境設定などで見かけるラベル:ユーザー入力可能なビューが規則的に並ぶレイアウトはNSGridViewを使うと楽に実装できます。

f:id:tid-a24:20190310150537p:plain

NSStackViewでも実装できないことはありませんが、 アラビア語などの右から左に読む言語へのローカライズが発生すると辛いことになります。

NSGridViewで実装していれば自動で左右を入れ替えてくれます。

実行結果

  • English f:id:tid-a24:20190310152649p:plain

  • Arabic f:id:tid-a24:20190310152221p:plain

参考

developer.apple.com

どのViewを使うべきかわかりやすく解説されています。

MapKitとCoreLocationで現在地の地図を表示する

Xcode 9.2 / Swift 4.0.3

現在地情報はシミュレーターのデフォルト値を使用しています。

MapKit

地図を表示する

f:id:tid-a24:20180310130658p:plain:w240

import UIKit
import MapKit

class ViewController: UIViewController {
    lazy var mapView: MKMapView = {
        let mapView = MKMapView(frame: view.frame)
        return mapView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
    }
}

CoreLocation

現在地を取得

info.plistに権限設定を追加します。 Valueには使用用途を説明する文章を入力する必要があります。 ここに入力した文字列はユーザーに表示されます。

f:id:tid-a24:20180313004449p:plain

権限設定 説明
NSLocationWhenInUseUsageDescription 使用中のみ許可
NSLocationAlwaysAndWhenInUseUsageDescription 常に許可、使用中のみ許可
NSLocationAlwaysUsageDescription 常に許可

iOS11 から使用中のみ許可(NSLocationWhenInUseUsageDescription)のサポートが必須となっているので上2つのどちらかを使うことになると思います。 使用中のみ許可と常に許可の両方をサポートして、それぞれ違う説明文を表示する場合だけNSLocationWhenInUseUsageDescriptionNSLocationAlwaysUsageDescriptionを指定します。

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController {
    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.delegate = self
    }
}

extension ViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .authorizedWhenInUse:
            locationManager.startUpdatingLocation()
        default:
            break
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // locationsに現在地が入っています
    }
}

MapKit + CoreLocation

現在地の地図を拡大して表示する

f:id:tid-a24:20180310130520p:plain:w240

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let coordinate = locations.last?.coordinate {
        // 現在地を拡大して表示する
        let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        mapView.region = region
    }
}

MKCoordinateSpanに指定しているlatitudeDelta / latitudeDelta の値を小さくするほど拡大されます。

地図上の現在地にピンをつける

f:id:tid-a24:20180310131006p:plain:w240

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let coordinate = locations.last?.coordinate {
        // ピンをつける
        let pin = MKPointAnnotation()
        pin.coordinate = coordinate
        mapView.addAnnotation(pin)

        // 現在地を拡大して表示する
        let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        mapView.region = region
    }
}

try! Swift 2018 Tokyoに参加しました | Day 2

try! Swift 2018 Tokyo 2日目のスライドなどのまとめ+企業ブースメモ

Expression Problem を解決する | Day 2-1

bkase.github.io

Swift もくもく会 in Barcelona | Day 2-2

speakerdeck.com

Swift によるアルゴリズムの可視化 | Day 2-3

speakerdeck.com

Kitura で Codable ルーティング | Day 2-4

-

超解像+CoreML+Swiftを使ってアプリの画像データ転送量削減に挑戦する | Day 2-5

speakerdeck.com

漫画をサーバー側で低解像度化してクライアント側で高解像度化するお話。 OSSとして公開、残念ながらモデルは大人の事情で非公開だそうです。 モデルを作成するスクリプトは含まれているそうなので漫画の電子データを沢山もっていれば試せそうです。

github.com

iOSでCharlesを導入する | Day 2-6

Charles for iOS がレビュー中とのこと。無事リリースされて欲しいですね。

拡張現実における体験設計 | Day 2-7

www.dropbox.com

現実とはこれまで取り組んで来た中で一番巨大なView Controllerである。

Swift エンジニアのための Kotlin 入門 | Day 2-8

speakerdeck.com

Swift5のOwnershipに備える | Day 2-9

-

デジタル信号処理 in Swift | Day 2-10

speakerdeck.com

Codableが導く型安全な世界 | Day 2-11

speakerdeck.com

iOS / Swift における対話型インターフェースの作成 | Day 2-12

speakerdeck.com

UIImageView vs Metal | Day 2-13

www.slideshare.net

GPUのことを考えるきっかけになるセッションでした。 ゲームを作っていたころは、ずっとGPUのこと考えてる状態でしたが最近はあまり考えてません。

  • コマンドを送る時は1回にまとめる。
  • テクスチャはGPUにキャッシュさせる。

などは、基本的にMetalだけでなくOpenGLDirectXでも同じです。

Swiftが動くDockerコンテナの各OSの性能比較 | Day 2-14

speakerdeck.com

型とパフォーマンスで見るType-erasureの利点 | Day 2-15

github.com

Make faces big by Vision and CoreGraphics | Day 2-16

-

開発者ツールと経験への時間投資 | Day 2-17

github.com

企業ブース

Yahoo! JAPANさんのペアプロ実演を見学しました。
作業の流れはざっくりこんな感じだったと思います。使用されてるタスク管理ツール名は忘れました。

  1. (ツール)タスクの確認
  2. コスト出し(1~3をじゃんけんみたいに指で同時に出して、食い違っていたら口頭ですり合わせしてからツールに記載)
  3. (ツール)該当タスクを開始状態にする
  4. ナビゲーターがテストを書く
  5. ドライバーが実装を書く
  6. テストをパスするまで頑張る
  7. コミット
  8. (ツール)該当タスクを完了状態にする
  9. (ツール)プロジェクトオーナーが該当タスクを承認する

ドライバー/ナビゲータで片方がユニットテストを書いて、もう片方が実装を書く形で進めてるとのことでした。

コードレビュー => ペアプロの作業内で確認してるので改めてレビューはしていない。ひとり作業が発生した場合は別途行う。
ドキュメントはない => メンバー全員で入れ替えながらペアプロですすめているから全員が同時にやめない限りどうにかなる
作業環境=>1台のMac + セカンドディスプレイ
Rx => 一時期使ってたが今は使っていない

書いてて思いましたが、スニペット/テンプレートやキーバインドなどなど個人間で異なるだろう環境設定をどうしてるのか気になります。

追記

そういえばテストをするたびにマウスカーソルを操作して個別にテストを選択されていました、
あそこはショートカットを使うとマウスカーソルを移動する必要がなくなります。

コマンド ショートカット 説明
Test ⌘⌥^ + U カーソルがあるテストを実行
Test Again ⌘⌥^ + G 前回実行したテストを再実行

try! Swift 2018 Tokyoに参加しました | Day 1

www.tryswift.co

try! Swift 2018 Tokyoに参加したので各セッションのスライドなどをまとめます。

2016, 2017に引き続き今年もniwatakoさんが聞き起こしをしてくださってるので細かいことはそちらを参照するといいと思います。

niwatako.hatenablog.jp

裏 Swift Tour | Day 1-1

github.com

SIL入門 | Day 1-2

Clang モジュールの探検 | Day 1-3

twitter.com

speakerdeck.com

Relative Pathsが使えないのくだりは私も困ってるので共感できました。
これができないのでKannaはPlaygroundが使えません。(libxml2のヘッダパスが解決できない)

レスポンダチェーンを知ろう | Day 1-4

-

関心の分離と単純化のためのSwiftコードの最適化 | Day 1-5

github.com

コーダーがデザインすべきなのか | Day 1-6

speakerdeck.com

Event driven networking for Swift | Day 1-7

ノンブロッキングIOをSwiftで扱うお話。
セッションの最後にSwiftNIOがOSSとして公開されました。

github.com

KituraやVaporがSwiftNIOを活用するのか興味がありますね。

変性のダイヤモンド | Day 1-8

speakerdeck.com

SwiftyPi | Day 1-9

speakerdeck.com

Raspberry PiでSwiftを動かしたお話。
Swift 4はARMv7に対応していないのでSwift 3.1.1とのこと。

github.com

Swift 4での32bit CPUサポートは誰かが頑張らない限りされそうにないですね。

[SR-124] Port Swift to Linux x86 32-bit - Swift

我が家を支えるSwiftの技術 | Day 1-10

speakerdeck.com

Swiftを使って家庭内で使用するアプリやWebサーバーを作ってるお話。
どうもWebサイトのパースに拙作のKannaも活用していただけてるようで、
お役に立てていれば嬉しいです。
(Swift 4に対応したリリースを出し忘れてることを思い出したので出しました)

UI Test の楽しさとメリット | Day 1-11

speakerdeck.com

ブロックチェーンのクライアントをSwiftで実装する | Day 1-12

speakerdeck.com

Protocol Oriented WebAPI Abstraction | Day 1-13

-

👾 | Day 1-14

speakerdeck.com

XcodeApple Watchに特化したゲーム開発ツール。

ゲームとRxは相性がいいのでRxSpriteKitを作ったそうです。

github.com

AST メタプログラミング | Day 1-15

speakerdeck.com

個人的に今回のtry! Swiftで一番面白かったです。

github.com

iOSでAlureを使うための準備

こちらを参考にビルドします。私の環境では修正が必要な箇所が異なっていたのでメモ。

flat-leon.hatenablog.com

AlureをiOSシミュレーター向けにビルドする

cmake . -DCMAKE_TOOLCHAIN_FILE=./ios-cmake/toolchain/iOS.cmake -DIOS_PLATFORM=SIMULATOR64
  • CMakeCache.txt を修正

includeパス指定が;で連結されている場所を(半角スペース)に置換する。

  • make
make
  • libalure-static.a を *.o に展開
ar -x libalure-static.a
  • *.o ファイルを全てXcodeプロジェクトに追加。

Alureを実機向けにビルドする

上記手順で作成されるライブラリはシミュレーター向け(x86_64アーキテクチャのみ対応)となっているため実機での実行ができません。 実機で実行できるようにするには実機向けにビルドする必要があります。

手順は同じでCMakeのオプション(最後の部分)を書き換えるだけです。

cmake . -DCMAKE_TOOLCHAIN_FILE=./ios-cmake/toolchain/iOS.cmake -DIOS_PLATFORM=IOS

今度は実機のアーキテクチャ(armv7/armv7s/arm64)にしか対応していないため、シミュレーターで実行できなくなります。

一度のビルドでarmv7/armv7s/arm64/x86_64全てに対応させるオプションは無いようなので シミュレーター向けと実機向けのライブラリを自分で統合します。

ユニバーサル対応する

lipoコマンドでシミュレーター向けライブラリと実機向けライブラリを統合してユニバーサルライブラリを作成します。

xcrun -sdk iphoneos lipo -create [実機向けライブラリ] [シミュレーター向けライブラリ] -output [出力パス]

全ての *.o ファイルに対して上記コマンドを適用したら完了です。

出来上がったライブラリをXcodeプロジェクトに追加するとシミュレーター/実機の両方で実行できるようになります。

補足

iTunes Storeに申請する場合は、シミュレーター用ライブラリを除外する必要があるので注意が必要です。

Bitriseでオーディオ再生をテストする(iOS)

趣味で音楽プレーヤーアプリを作り始めたのでBitriseをセットアップしました。
初回テストを兼ねて最初にオーディオを再生する簡素なコードとテストを書いてCIを動かしたところテストが失敗しました。 (もちろんローカル環境でのテストはパスしている状態です。)

エラーログは下記の通り。

[AudioHAL_Client] AudioHardware.cpp:875:AudioObjectAddPropertyListenerBlock:  AudioObjectAddPropertyListenerBlock: no object with given ID 0
[AudioHAL_Client] AudioHardware.cpp:875:AudioObjectAddPropertyListenerBlock:  AudioObjectAddPropertyListenerBlock: no object with given ID 0
[DDAgg] DefaultDeviceAggregate.cpp:737:BuildAggregate: Error finding valid input or output devices!
[AudioHAL_Client] AudioHardware.cpp:2682:AudioDeviceStop:  AudioDeviceStop: no device with given ID
[aqme] 318: error -66680 finding/initializing AQDefaultDevice
[aurioc] 918: failed: -10851 (enable 2, outf< 2 ch,  44100 Hz, Int16, inter> inf< 2 ch,      0 Hz, Int16, inter>)
[AudioHAL_Client] AudioHardware.cpp:2682:AudioDeviceStop:  AudioDeviceStop: no device with given ID
[aqme] 318: error -66680 finding/initializing AQDefaultDevice
108: * * * NULL AQIONode object
771: Can't make UISound Renderer

有効な入出力デバイスが見つからないと言っていますね。 Bitriseでオーディオ再生を伴うテストをする場合、audio input/output deviceを作る必要があるみたいです。

オーディオデバイスを作成する手順

Homebrewでインストールすることができます。

  1. WorkflowにScript stepを追加
  2. Script contentを記入
#!/usr/bin/env bash
set -ex
brew install Caskroom/cask/soundflower
brew install switchaudio-osx
SwitchAudioSource -s "Soundflower (2ch)" -t input
SwitchAudioSource -s "Soundflower (2ch)" -t output

f:id:tid-a24:20180124053334p:plain

この状態でリビルドすると失敗していたテストケースが全て成功するようになりました。

参考

discuss.bitrise.io

macOS appのCloudKitでカスタムコンテナを使う時に気をつけること

環境:Xcode 8.3.3 / Swift 3.1

追記 デフォルトコンテナとカスタムコンテナが一致している場合はCKContainer.default()が使えて、
一致していない場合はコンテナ名を指定する必要がある、、、ということでした。

問題

macOS appでCloudKitを使う際、Capabilitiesでカスタムコンテナを1つだけ指定してから
CKContainer.default() でレコードを保存しようとすると下記エラーが出ました。

"Server Rejected Request" (15/2001); "Request failed with http status code 500"

コードはこんな感じです。

let database = CKContainer.default().privateCloudDatabase
let record = CKRecord(recordType: "Memo")
record.setValue("Hello!", forKey: "message")
database.save(record) { _, _ in
}

解決方法

どうやらmacOS appでカスタムコンテナを使う場合、
CKContainerインスタンスを作る時にコンテナ名を指定しないといけないようです。

let database = CKContainer(CUSTOM_CONTAINER_NAME).privateCloudDatabase

なお、iOSでカスタムコンテナを使う場合はCKContainer.default()が問題なく使用できます。
勘違いでした。CKContainer.default()だとCapabilitiesのカスタムコンテナの指定は無視され、
Use default containerを指定した場合と同じコンテナが使われます。
macOS appだとデフォルトコンテナが自動作成されないようで、そのためにエラーが発生していました。

リファレンス等で確認できませんでしたが、ハマったのでメモとして残しておきます。