学生の頃から4年半付き合ってきた彼女と結婚しました。
区役所に婚姻届を提出し、両家の両親らをお招きして食事会を催しました。
もう2年ほど同棲しておりましたので、これといって変わることもありませんが、よりいっそうがんばって参りたいと思います。
今後ともよろしくおねがいいたします。
以前に書いたように、watchOS 1.0においてWatchKitアプリはiPhoneの側でそのプロセスが動作する。Apple Watch側にあるのはUIリソースのみであり、実際の処理はiPhone上で行われる。これは非常に高度な仕組みでありながらも、パフォーマンス上の問題を抱えていた。watchOS 2.0からはこれが大きく変化して、いわゆるネイティブ化が行われ、実際の処理もApple Watch側で行われるようになる。とはいってもAPI上の変化は少なく、これまでとおおよそ同じようなインターフェースになっている上、ベースはiOSであるからUIKitの一部やFoundationが利用できる。
watchOS 2.0の正式なリリースは秋を予定しており、Apple Watchの発売からおおよそ半年である。しかしここで大きな疑問が残る。watchOS 1.0のアーキテクチャはいったい何のために用意されたのだろうか。ネイティブ化されるまでの半年間はいったい何のために必要だったのだろうか。Apple Watchに搭載されている一部のApple製のアプリは最初からネイティブで動作しているはずだが、はじめからその方法を解放できなかったのはなぜなのか。
iOS 9から加わる新しい仕組みの中に、App Thiningと呼ばれるものがある。アプリがユーザーの端末にダウンロードされる前に、App Storeがアプリに最適化を加えて余分なリソースを減らしたり、必ずしも必要とは限らないリソースを後からダウンロードさせたりといった仕組みである。その中に、Bitcodeと呼ばれる手法が存在する。
Bitcodeでは、デベロッパーはアプリの実行ファイルをLLVM Bitcode状態でApp Storeに(iTunes Connectに)アップロードする。LLVM BitcodeとはコンパイラであるLLVMの中間表現を格納するフォーマットで、Appleの独自の規格というわけではなく、LLVMプロジェクトの中で定められたフォーマットである。LLVMの中間表現は、このあと本来はLLVMによって最適化され、それぞれのプロセッサのアーキテクチャのネイティブコードに変換されるはずである。Bitcodeによる提出ではこの最終工程をApp Storeが行うので、将来さらに高度な最適化ができるようになったり、あるいはプロセッサのアーキテクチャが変わったときにも、デベロッパーによるバイナリの再提出なしに対応できるという寸法である。
Bitcodeでの提出は、iOSアプリではいまのところ任意、watchOSのネイティブアプリでは必須とされている。
現在のApple WatchはApple S1というSiPである。Chipworksによれば、内蔵されているプロセッサにはAPL0778
と書かれているようだ。Appleはこのプロセッサのアーキテクチャを公表していない。
総合的に見て、AppleはApple Watchのアーキテクチャを流動的に保とうとしているように思われる。アプリのネイティブ化はBitcodeの導入を待っていたように感じられるし、Bitcodeがあればアーキテクチャに縛られることがない。
ウェアラブルデバイスにおける共通の問題はバッテリーの保ちである。Appleがこれに対して今後どのようなアプローチを取ってくるのか目が離せない。
ひとより先に未来に辿り着けるのだとしたら、買わない理由がどこにあるのだろう。
iPhone 4から入ったiOSアプリデベロッパよりiPhone 3Gからやってるデベロッパの方が強そう ← みなさんがいますぐApple Watchを買う理由です
— Hiroki Kato (@cockscomb) 2015, 5月 2
第61回 Cocoa勉強会関西で“Swift 1.2 The long-awaited language updates”と題して発表した、Swift 1.2の主だった(おもしろい)変更点の紹介です。
Swift 1.2で最も改善されたのはif文です。if let
でOptionalをunwrapできる機能が大きく向上し、複数のOptionalを同時にunwrapできるほか、unwrapされた値について条件を加えることができるようになりました。
例えばcondition: Bool
が真でふたつのOptional<Int>
がnil
ではなく、大小関係にも条件がある、という条件を表してみます。
let condition = true let aNumber: Int? = 3 let anotherNumber: Int? = 7 if condition { if let a = aNumber { if let b = anotherNumber { if a < b { println(a + b) } } } }
let condition = true let aNumber: Int? = 3 let anotherNumber: Int? = 7 if condition, let a = aNumber, let b = anotherNumber where a < b { println(a + b) }
極端な例ではありますが、Swift 1.1ではifを4回重ねていたものを、Swift 1.2では1つのifで表現できます。
let
定数は再代入できません。Swift 1.1ではそのために、宣言と同時に値を決める必要がありました。Swift 1.2ではその制限が少し緩和され、値が使われる前に確実に初期化されるなら、宣言の後で値が決まってもよいことになりました。
let hour = NSCalendar.currentCalendar().component(.CalendarUnitHour, fromDate: NSDate()) var meridiem: String if hour < 12 { meridiem = "AM" } else { meridiem = "PM" } println(meridiem)
let hour = NSCalendar.currentCalendar().component(.CalendarUnitHour, fromDate: NSDate()) let meridiem: String if hour < 12 { meridiem = "AM" } else { meridiem = "PM" } println(meridiem)
Swift 1.1ではvar
にしなければならなかったmeridiem
が、Swift 1.2ではlet
にできます。ifに限らずswitchなども利用できますが、全ての条件で初期化されることが保証されている必要があります。
CollectionType
にArray
やDictionary
などに加えてSet
が追加されました。Setは集合を表し、Foundation.frameworkのNSSet
に対応します。Setの導入に合わせて、Objective-CでNSSetを利用していたAPIをSwiftから利用する場合にはSwiftのSetを使うことになります。
let abc: NSSet = NSSet(objects: "A", "B", "C") if NSSet(objects: "A").isSubsetOfSet(abc) { println(abc) }
let abc: Set<String> = ["A", "B", "C"] if abc.isSupersetOf(["A"]) { println(abc) }
NSSetと違って内包する値の型を指定でき、またArrayリテラルで表現できます。
Swift 1.2からclass
にもstatic
な関数やプロパティを持てるようになりました。これまでもclass func
がありましたが、static func
はclass
かつfinal
という意味になります。またstatic let
で宣言した定数は最初にアクセスされた時点で評価されるという特徴を持ちます。
class StaticPropertiesAndMethods { static func printDate() { println(date) } static let date = NSDate() } StaticPropertiesAndMethods.printDate() // => "2015-04-10 07:01:00 +0000"
クラス変数はObjective-Cには存在せず代替的な手法で実現していましたが、Swiftでついに用意された文法と言えます。
Swift 1.1でクロージャを使うときに、返り値としてVoid
を要求するところに1行だけ、Voidではない値を返す式があると、コンパイルエラーになっていました。これはクロージャが1行だけの式を持つとき、その式の結果がクロージャ全体の返り値として取り扱われ、型がミスマッチだと判定される結果です。このために無意味なreturn
を付け加えるなどして回避していました。
Swift 1.2ではこのようなときにVoidではない値が返っても許容されるようになりました。これで意味上は不要なreturn
を書く必要がなくなり、すっきりします。
let wantsVoid: (() -> Void) = { "non-Void here" return }
let wantsVoid: (() -> Void) = { "non-Void here" }
Swift 1.2ではzip
関数が導入されました。ふたつのSequenceType
の組み合わせを簡単に作ることができます。要素数が異なる場合には短い方に合わせられます。
let ordinals = ["first", "second", "third"] let values = [1, 2, 3] var ordinalsDict: [String: Int] = [:] for (key, value) in zip(ordinals, values) { ordinalsDict[key] = value } println(ordinalsDict) // => ["first": 1, "second": 2, "third": 3]
キャスト関連でも非互換な変更などがあります。
let any: AnyObject = 3 let number: Int = any as! Int
危険な操作であるときの一貫性のため、ダウンキャストするときas!
を使うことになりました。
protocol SomeProtocol { func something() -> String } class SomeClass: SomeProtocol { func something() -> String { return "Something awesome" } } let some: AnyObject = SomeClass() if some is SomeProtocol { println((some as! SomeProtocol).something()) }
@objc
ではない通常のprotocolにもキャスト関連の操作ができるようになっています。
関数が引数としてクロージャを受け取るとき、クロージャの内部で利用される変数はキャプチャされ、それぞれ強参照された状態となります。またこのためにクロージャの外のオブジェクトのプロパティなどを利用する場合にはself.
で修飾する必要があります。これによってクロージャを実際に評価するタイミングはいつでもよいことになっています。
しかしクロージャを受け取ってすぐに評価することが決まっている関数の場合はどうでしょう。そういうときはキャプチャする必要が無いし、self.
も不要です。キャプチャしない方がパフォーマンス上でも有利でしょうし、最適化しやすいかもしれません。このようにすぐに評価されることがわかっているクロージャのために追加された新しいアノテーションが@noescape
です。
func map<U>(f: @noescape (T) -> U) -> U?
Optional
のmap
では上記のように@noescape
されています。このためクロージャで使う変数がプロパティであってもself.
は不要です。
このようにSwiftではこれらの挙動をescapeという言葉で表しています。
func map<U>(transform: (T) -> U) -> [U]
ところでArray
のmap
を見てみると、上記のように@noescape
アノテーションがないことが分かります。一貫性がないように思われるかもしれませんが、これには理由があるように思えます。lazy()
関数などを用いて遅延評価する場合にはescapeせざるを得ません。これは簡単な実験で確かめられます。
let start = NSDate() let array = Array(0..<3) let result = lazy(array).map { (number: Int) -> Int in sleep(1); return 100 * number } println(NSDate().timeIntervalSinceDate(start))
このようにlazy()
関数を使えばmap
中でsleep(1)
していても一瞬で実行が終わり、result[2]
などresult
の要素にアクセスしたとき初めてmap
のクロージャが評価されます。このlazy()
を消すと、このスニペットは少なくとも3秒の実行時間を必要とします。
これがArray.map
がescapeを必要とする理由です。
この@noescape
が追加されたのに合わせてデフォルトでは@autoclosure
もescapeされなくなりました。escapeしてほしい場合には@autoclosure(escaping)
と書く必要があります。
新しいflatMap
関数を使うとmap
ではできないちょっとしたことが簡単にできるようになります。
let mapped = ["A B C", "D E", "F"].map { $0.componentsSeparatedByString(" ") } println(mapped) // => [[A, B, C], [D, E], [F]] let flatMapped = ["A B C", "D E", "F"].flatMap { $0.componentsSeparatedByString(" ") } println(flatMapped) // => [A, B, C, D, E, F]
上記の例のように、Arrayの要素に何らかの変換を行うとき、flatMap
なら要素毎に新たなArrayを返しても2次のArrayになりません。
func toInt(value: String) -> Int? { return value.toInt() } let one: String? = "1" let mapOne = one.map { toInt($0) } println(mapOne) // => Optional(Optional(1)) let flatMapOne = one.flatMap { toInt($0) } println(flatMapOne) // => Optional(1)
また上記の例では、Optional
のmap
とflatMap
の違いを示しています。map
の中でさらにOptionalな値を返すとOptional<Optional<Int>>
を得ることになります。flatMap
ではOptional<Int>
です。これは非常に有用であることがわかるでしょう。
このようにflatMap
はmap
とよく似ていますが、最初のArrayと要素数が変わったArrayが返ったり、多重のOptionalを避けられたりといった特徴があります。
let intLike = ["12.3", "45", "6-7-8"] let results = intLike.flatMap { $0.toInt().map { [ $0 ] } ?? [] } println(results) // => [45]
上記はこれの応用例で、ふつうのmap
であればArray<Int?>
が返るところを、nil
を除去してArray<Int>
にしています。
Swift 1.2ではFoundation.framework
にも少し拡張が増えています。
let fonts = "/System/Library/Fonts" let files = NSFileManager().enumeratorAtPath(fonts)! for path in files { println(path) }
enumeratorAtPath
はNSDirectoryEnumerator
というNSEnumerator
のサブクラスを返します。Swift 1.2より前では、NSEnumerator
はSequenceType
ではなく、for in
が使えませんでした。Swift 1.2からはgenerator()
メソッドが加わり、protocol SequenceType
を満たすようになったためfor in
できるようになりました。
Swiftはこのように、Foundation.framework
についてSwiftから利用しやすいように多少の拡張を加えており、今後もこれらが拡充されていく可能性があります。
NSString
などのFoundation.framework
のオブジェクトから対応するSwiftのString
などへの暗黙的な変換が行われなくなりました。明示的にas String
などと書いてキャストしておく必要があります。逆のString
からNSString
といった変換はこれまで通り暗黙的に行われます。
SwiftからObjective-Cのヘッダを見たときにはNSString
はString
になっており、Swiftからはほとんど常にString
を利用していることになるので、実際的にこれが問題になるケースは少ないでしょう。ただしCoreFoundation.framework
に関するオブジェクトを利用するときは別です。
要素数を数える関数の名前が変わりました。分かりやすいですね。
Swift 1.2と共に様々なパフォーマンスが改善されました。Swiftで書かれたプログラムのパフォーマンスが改善されたほかにも興味深い変更があります。
Swiftのコンパイルがインクリメンタルになりました。これまでは、ひとつのファイルを変更したときにも全てのファイルのコンパイルをやり直す必要がありましたが、これからは必要最低限のファイルをコンパイルし直すだけでよくなりました。ただし今後も、変更したファイルだけをコンパイルし直すのではなく、変更が影響するであろうファイル全てをコンパイルし直す必要があります。
An Xcode 6.3.1 issue can slow compile times for complex Swift projects. Xcode 6.4 beta has a fix.
For more info: https://t.co/Efl0dVcXRU
— Swift Language (@SwiftLang) April 29, 2015
Swift 1.2でも思ったよりコンパイルが遅いということがあるかもしれませんが、もしかするとXcode 6.3.1の問題かもしれません。
オブジェクト指向のパラダイムにおいて、クラスを継承してメソッドをオーバーライドするというのがあります。すなわち、メソッドを呼び出す際にはオーバーライドされた後の実装を呼び出す必要があるということです。プログラムの実行中、メソッドの呼び出しやプロパティを操作する時に、実際に呼び出すべき対象を動的に解決します。これをdynamic dicpatchと呼んでいます。
ここで、例えばfinal
修飾されているメソッドは、ぜったいにオーバーライドできません。そういった場合にはdynamic dispatchする必要がないので、Swiftのコンパイラは静的に解決してくれます。dynamic dispatchが行われない分、実行時のパフォーマンスは改善します。
さらにアクセス制御のprivate
で修飾されていれば、そのファイルの外からはオーバーライドできません。つまりそのファイル中でオーバーライドされているか検査することで不要なdynamic dispatchをしないようにコンパイルできます。private
修飾はパフォーマンスにも役立っていることがわかります。
さらにinternal
でもこれを可能にするのがWhole Module Optimizationです。internal
で修飾されていればそのモジュールの外からオーバーライドされません。つまり、モジュール全体を検査することで、不要なdynamic dispatchを避けることができます。この最適化は比較的時間がかかるため、現在のところオプションになっています。
これらの話題はSwiftのブログにも書かれています。
Swift 1.2に合わせてObjective-Cにも変更があります。変数や引数、返り値などがnil
を取り得るかをアノテートできるようになりました。nullable
やnonnull
で修飾することでこれが可能です。また全てにこれらのアノテーションを付けて回るのは現実的ではないので、NS_ASSUME_NONNULL_BEGIN
とNS_ASSUME_NONNULL_END
で囲われている部分はnonnull
ということになっています。基本的にはこれで囲んでおいて、nullable
だけ明示的に書くのがよいでしょう。
これらのアノテーションを書いておくと、Swiftから見たときにImplicitly Unwrapped OptionalではなくOptionalかそうでないかはっきりします。さらにObjective-Cにおいても、nonnull
とアノテートされたポインタにnil
を入れようとしていれば静的解析で検出されます。ただし実行時には影響しません。
Swiftはまだ変わり続ける生きた言語です。Swift 1.2はSwiftの正式なリリース以来もっとも大きな変化で、開発者にとっても非常に歓迎すべき改善が数多くあります。今後のSwiftの進化に期待しながら、Swift 1.2でバリバリ開発していきたいですね。
いっしょにSwiftでバリバリ開発しませんか。
UTI とは Uniform Type Identifier のことで、iOS や OS X の世界で、ファイルなどの種類を表すために用いられる文字列です。
UTI の実例として、画像一般を表す public.image
や、そのサブタイプで JPEG 画像を表す public.jpeg
などがあります。iOS や OS X で利用されるほとんどのファイル形式には対応する UTI があり、例えば Keynote ファイルは com.apple.iwork.keynote.key
などとなります。もちろんサードパーティも例外ではなく、Adobe Photoshop のファイルは com.adobe.photoshop-image
となり、Microsoft Word の docx
は org.openxmlformats.wordprocessingml.document
です。
iOS や OS X のフレームワークは、ファイルの種類を表すのにこの UTI を利用することが多くあります。例えば Photos.framework
から画像データを得たいというとき、PHImageManager
の requestImageDataForAsset(_:options:resultHandler:)
メソッドを利用します。この resultHandler
は ((NSData!, String!, UIImageOrientation, [NSObject : AnyObject]!) -> Void)!
という型のクロージャで、クロージャの第2引数はドキュメントを読むと dataUTI
となっています。すなわち、画像データの種類が UTI として得られます。
ところで、この UTI から拡張子や MIME type や拡張子を取得するにはどうすればよいのでしょうか。画像をサーバーにアップロードするときには MIME type が必要かもしれません。例えば public.jpeg
だったら image/jpeg
という風な対応を丁寧に処理しても良いかもしれませんが、そもそも iOS や OS X にはそういった関数が備わっています。func UTTypeCopyPreferredTagWithClass(inUTI: CFString!, inTagClass: CFString!) -> Unmanaged<CFString>!
を利用することで UTI から MIME type や拡張子が得られます。
let mime = UTTypeCopyPreferredTagWithClass("public.jpeg", kUTTagClassMIMEType).takeRetainedValue() // => "image/jpeg" let ext = UTTypeCopyPreferredTagWithClass("public.jpeg", kUTTagClassFilenameExtension).takeRetainedValue() // => "jpeg"
またここでは紹介しませんが、逆に MIME type や拡張子から UTI を得る関数もあります。非常に便利ですが、ごくたまにしか使わないので忘れがちです。筆者は毎回のように忘れて、過去のプロジェクトや IRC のログを検索して思い出します。
ということで、このような UTI に関連した処理を利用しやすくするラッパーライブラリが、UTIKit です。これを利用すると上記の処理が以下のようになります。
UTI("public.jpeg").MIMEType // => "image/jpeg" UTI("public.jpeg").filenameExtension // => "jpeg"
ごく短くなる上に、インターフェースもわかりやすいですね。
もちろん MIME type や拡張子から UTI を得るのも簡単です。
let jpeg = UTI(MIMEType: "image/jpeg") let jpeg = UTI(filenameExtension: "jpeg")
Equatable を実装しているためこれらふたつは同値です。
UTI(MIMEType: "image/jpeg") == UTI(filenameExtension: "jpeg") // => true
また、public.jpeg
は public.image
に従っているので、switch
を利用して以下のように書けます。
switch UTI("public.jpeg") { case UTI("public.image"): println("JPEG is a kind of images") default: fatalError("JPEG must be a image") }
これらのように、UTI が Swift ライクに扱えることがわかります。
CocoaPods 0.36 以降でインストールできます。Podfile
に以下の行を書き足しましょう。
pod "UTIKit"
また Carthage でもインストールできるかもしれません(試してない)。
MIT ライセンスです。どうぞご利用ください。
[UTIKit by @cockscomb] UTIKit is an UTI (Uniform Type Identifier) wrapper for Swift. https://t.co/sW0yDUAL1H
— CocoaPods New Pods (@CocoaPodsFeed) 2015, 2月 21
人類の多年の夢であったNSWindow
のタイトルバーのカスタマイズであるが、YosemiteになってついにオフィシャルなAPIが実装された。
YosemiteではApple製のアプリケーションほとんどにおいてデザインが一新され、かつてOS Xを特徴付けていたツルッとしたテクスチャは取り払われた。そしてウインドウのタイトルバーまでもが変わった。
ウインドウのタイトルバーは、OS XにおいてGUIアプリケーションの要である。タイトルバー自体がウインドウを移動するためのノブであり、左にはウインドウを操作するための赤・黄・青のコントロールが、中央にはウインドウのタイトルが表示される。ときにはツールバーと見かけ上くっついたりするが、いずれにしてもウインドウの中心はタイトルバーである。
そのタイトルバーをカスタマイズしたい、これは全人類が生まれながらに持っている当然の欲求である。そして、しかし、Appleは長年にわたってそういったAPIを提供してこなかった。ウインドウをウインドウたらしめるタイトルバーを、心ないデベロッパーが欲望のままに毀損することを許さないというように、禁断の果実を決して手放そうとはしなかった。それがYosemiteになって、Apple製のアプリケーションでさえ、一般的なタイトルバーのルールを破壊した。いまやタイトルバーには信号機のようなコントロールを残して、タイトルの代わりにツールバーが表示されたり、あるいはウインドウのコンテンツがそのまま表示されるようになった。これはいまやAppleだけの特権ではなく、ついにAPIが提供されたのである。
OS X 10.10 YosemiteからNSWindow
に新しく追加されたプロパティvar titleVisibility: NSWindowTitleVisibility
を使うと、ウインドウのタイトルを非表示にできる。enum NSWindowTitleVisibility
は以下のように二つの値からなる。
enum NSWindowTitleVisibility : Int { case Visible case Hidden }
デフォルトは.Visible
であるが、ここでヘッダから.Hidden
のコメントを引用する。
The always hidden mode hides the title and moves the toolbar up into the area previously occupied by the title.
NSToolbar
がある場合にはタイトルがあった場所に移動するようだ。これによって、例えばSafariやカレンダーのように、タイトルバーの高さが少し高く、信号機のようなコントロールのすぐ右にツールバーがあるようなウインドウが作れる。NSWindow
にNSToolbar
を設定して、あとは以下のようなコードをNSWindowController
のfunc windowDidLoad()
などに実装する。
window?.titleVisibility = .Hidden
OS X 10.10 YosemiteではNSViewController
が大幅に強化され、iOSのUIViewController
から多くの思想を受け継いだ。そして同時にNSViewController
のサブクラスがいくつか追加された。その一つがNSTitlebarAccessoryViewController
である。
NSTitlebarAccessoryViewController
は、その名前が示す通り、タイトルバーを装飾するViewのControllerである。このViewControllerのview
プロパティが返すViewが、NSWindow
のタイトルバーに表示される。合わせてNSWindow
にも以下のようなプロパティやメソッドが追加されている。
var titlebarAccessoryViewControllers: [AnyObject] func addTitlebarAccessoryViewController(childViewController: NSTitlebarAccessoryViewController) func insertTitlebarAccessoryViewController(childViewController: NSTitlebarAccessoryViewController, atIndex index: Int) func removeTitlebarAccessoryViewControllerAtIndex(index: Int)
NSWindow
は複数のNSTitlebarAccessoryViewController
を順序付きで管理する。タイトルバーの中でどの部分に表示されるのかは、NSTitlebarAccessoryViewController
の持つvar layoutAttribute: NSLayoutAttribute
というプロパティで決定される。ただし現在の所NSLayoutAttribute
のうち有効な値は.Bottom
と.Right
のみであることがヘッダに書かれている。もちろんそれぞれに対応する位置は、タイトルバーの下と右である。
実際に表示するには以下のようなコードを、例えばNSWindowController
のfunc windowDidLoad()
に実装する。NSTitlebarAccessoryViewController
のインスタンスはStoryboardから生成している。
let accessory = storyboard?.instantiateControllerWithIdentifier("TitlebarAccessory") as NSTitlebarAccessoryViewController accessory.layoutAttribute = .Right window?.addTitlebarAccessoryViewController(accessory)
タイトルバーの位置までウインドウのコンテンツを表示するために、新たにvar titlebarAppearsTransparent: Bool
プロパティと、NSFullSizeContentViewWindowMask
定数が用意された。
titlebarAppearsTransparent
はタイトルバーの背景を透明にするかどうかを表す。NSFullSizeContentViewWindowMask
は、NSWindow
のvar styleMask: Int
プロパティに利用する定数で、タイトルバーの領域までをコンテンツの領域とすることを表現するものである。
NSFullSizeContentViewWindowMask
とNSScrollView
の組み合わせによって、タイトルバーの下にコンテンツが透けるような表現が可能になる。またNSFullSizeContentViewWindowMask
を設定した上でtitlebarAppearsTransparent
を真にすると、信号のようなコントロールだけのウインドウが作れるのだ。
window?.titleVisibility = .Hidden window?.styleMask |= NSFullSizeContentViewWindowMask window?.titlebarAppearsTransparent = true
有史以来、NSWindow
のタイトルバーをカスタマイズすると言えば大きく二つの方法があり、ひとつはウインドウのコントロールまで完全に自作して自由に表示する方法、そしてもうひとつはwindow.contentView.superview
という風にNSWindow
の実装に依存してビューの階層を辿る方法である。現実的には主に後者が利用されることが多かったのではないだろうか。(NSThemeFrame
などで検索するとそういった方法を指し示すページが今も見つかるだろう。)
しかしOS X 10.10 Yosemiteでは、このビューの階層を辿る方法を使っていると"NSWindow warning: adding an unknown subview:"
というログが出力されるようになり、代わりにオフィシャルな方法が提供されることになった。このことはAppKitのリリースノートに記載されている。
ここまでで、OS X 10.10 Yosemiteから追加されたAPIによって、柔軟にタイトルバーをカスタマイズできることがわかった。これらはもちろん組み合わせることもできる。例えばSafariでは、titlebarAppearsTransparent = true
とした上でlayoutAttribute = .Bottom
のNSTitlebarAccessoryViewController
を設定しているかもしれない。
また昨今噂されるUXKitでは、UXNavigationController
などUIKit由来の機能が存在するらしい。これらのNSWindowの新しいAPIを使って、例えばUINavigationBar
相当の機能をタイトルバーに押し込めることもできるのではないだろうか。
まだ見ぬOS X 10.11への期待を膨らませつつ、読者諸氏もNSWindow
のタイトルバーをカスタマイズしてみてほしい。こちらからは以上だ。