画像をJPEGに変換するのはとっても簡単。
なんらかの方法でNSBitmapImageRepを作成。
let imgRep = NSBitmapImageRep(...)
あとは、NSBitmapImageFileType.NSJPEGFileType を指定して Data を取得するだけ。
properties には圧縮率などを辞書として渡せます。
let data = imgRep.representation(using: NSBitmapImageFileType.NSJPEGFileType, properties: [NSImageCompressionFactor:Float(0.8)])
最後に data を適当な場所に書き出せばOKです。
data.write(to: pathString, options:[])
NSImageをリサイズ
意外と複雑な画像のリサイズ。
やり方は色々ありそうですが、今回使った方法を記録しておきます。
大まかな流れは
まずはあらかじめ準備したNSImageからNSBitmapImageRepを取り出し、さらにCGImageを取り出す。
guard let image = NSBitmapImageRep(data: sourceImage.tiffRepresentation!)?.cgImage else {
// エラー処理
}
次に各種パラメータを準備して、CGContextを作成。
let bitsPerComponent = 8
let bytesPerRow = 4 * newWidth
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let bitmapContext = CGContext(data: nil, width: newWidth, height: newHeight, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
// エラー処理
}
サイズを指定して書き込む。
let bitmapSize = NSMakeSize(CGFloat(newWidth), CGFloat(newHeight))
let bitmapRect = NSMakeRect(0.0, 0.0, bitmapSize.width, bitmapSize.height)
bitmapContext.draw(image, in: bitmapRect)
最後にNSImageに戻す。
guard let newImageRef = bitmapContext.makeImage() else{
// エラー処理
}
let newImage = NSImage(cgImage: newImageRef, size: bitmapSize)
やり方は色々ありそうですが、今回使った方法を記録しておきます。
大まかな流れは
- CGImageを準備
- CGContextを準備
- リサイズ後のサイズを指定
- contextを使って書き込む
- NSImageに戻す
まずはあらかじめ準備したNSImageからNSBitmapImageRepを取り出し、さらにCGImageを取り出す。
guard let image = NSBitmapImageRep(data: sourceImage.tiffRepresentation!)?.cgImage else {
// エラー処理
}
次に各種パラメータを準備して、CGContextを作成。
let bitsPerComponent = 8
let bytesPerRow = 4 * newWidth
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let bitmapContext = CGContext(data: nil, width: newWidth, height: newHeight, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
// エラー処理
}
let bitmapSize = NSMakeSize(CGFloat(newWidth), CGFloat(newHeight))
let bitmapRect = NSMakeRect(0.0, 0.0, bitmapSize.width, bitmapSize.height)
bitmapContext.draw(image, in: bitmapRect)
最後にNSImageに戻す。
guard let newImageRef = bitmapContext.makeImage() else{
// エラー処理
}
let newImage = NSImage(cgImage: newImageRef, size: bitmapSize)
NSArrayControllerでNSTableViewをBind!
Touchdownでは複数の設定を保存して切り替えられるようにするため、NSTableViewをUIとして使います。
まずNSTableViewとArrayControllerを2箇所バインド。
ContentとはarrangedObjects、Selection IndexesはselectionIndexes。
さらにTableViewCellのValueにTableCellViewのobjectValueをバインド。
TableViewCellのBehaviorをEditableにしておきます。
基本的なとこだけど、何かと分かりにくいCocoa Binding。
iOSでは使えないらしく情報も少なめなので、簡単にまとめておきます。
ViewControllerにArrayを定義
NSTableViewは2箇所Bind
まずNSTableViewとArrayControllerを2箇所バインド。
ContentとはarrangedObjects、Selection IndexesはselectionIndexes。
さらにTableViewCellのValueにTableCellViewのobjectValueをバインド。
値を変更できるようにする
TableViewCellのBehaviorをEditableにしておきます。
設計と実装
きっちり設計して後は実装するだけ、というのはある意味理想形の一つかもしれませんが、勉強しながら進めたいので今回は作りながら考えることにします。
とはいえ、気の向くままにコードを書くわけにもいかないので自分ルールは決めておきたいところ。
・Macアプリ設計/実装の流儀に従う
・Cocoa Bindingは積極的に使う
・重い処理はマルチスレッド化する
まずはこのくらいから始めることにします。
アプリの要件洗い出し
何か作ろうと思ったら、まずは要件を整理してみるのが完成への近道。
とはいえ、個人で開発するだけならそれほど厳密になる必要もなく、まずは「最低限これがあればアプリとして成立する」というところを狙います。
譲れないのは以下の点。
あとはここから膨らませていく感じで。
とはいえ、個人で開発するだけならそれほど厳密になる必要もなく、まずは「最低限これがあればアプリとして成立する」というところを狙います。
譲れないのは以下の点。
- ドラッグ&ドロップで複数画像をまとめてJPEGに変換
- リサイズに対応
- 新規フォルダにまとめて保存
- 同名のファイルがある場合はリネーム
- 設定をプリセットとして保存し切り替えられること
- TouchBar対応
あとはここから膨らませていく感じで。
値を文字列と結合
値を文字列と結合したいときは
let a = 1
let result = "total " + String(a)
のように書くと、aから文字列が生成されるので文字列演算で結合できます。
よくあるコードなので簡単な書き方が用意されていて、
"total \(a)"
と、\()で値を囲むだけでもOKです。
インスタンスのキャストと同時に型チェック
インスタンスをダウンキャストするときに気になるのが型チェック。
Swiftではas?という便利なキャストが使えます。
?が付いていることから予想できる通りoptionalを返すため、キャストできないときはnilとなります。
オプショナルバインディングと組み合わせるのが良さそうですね。
if let a = b as? C {
// bがCクラスのインスタンスにキャストできる場合に実行される
}
高機能なenumをシンプルに使う
Swiftのenumは有限の集合を定義することに特化したクラスのように振舞います。
しかし、Cのenumのようにシンプルに使うことも可能。
enum MyType: Int {
case a
case b
case c
}
Intを継承するような形で書くと、a, b, cはそれぞれ0, 1, 2に対応します。
Intで取り出したい場合は
MyType.a.rawValue
と、rawValueでアクセス。
ちなみに
let type = MyType.a
ptint(type)
とすると、0ではなくaが出力されます。
enumと文字列の変換関数を実装する必要がないですね!
ArrayとDictionary
SwiftのArrayとDictionaryは覚えやすいですね。
書き方は色々ありますが、、、
let array: [Int] = [1, 2, 3]
let dict: [String : Int] = ["a" : 1, "b" : 2, "c" : 3]
[]の中にそのまま列挙すれば配列、key : value を列挙すれば辞書になります。
インクリメントするには
カウンターなどで変数の値を1増やすインクリメント処理。
他の言語ではおなじみの
i++
でインクリメント、、、できない!
i += 1
と書けばよいのですが、他に慣れてると変な感じです。
deferでクリーンナップ
エラー処理などのため関数の途中でreturnしたときに、クリーンナップ処理を書き忘れるのはよくある話。
排他制御でロック解除し忘れたりすると悲惨です。
そんなときに便利なのがdefer。
関数の中の任意の場所で
defer { ... }
としておくと、{}の中の処理を関数を抜ける直前に必ず実行してくれます。
ありがちなミスを防いでくれる便利な機能ですね。
Nil Coalescing ??
nilを扱うのに利用するoptionalですが、Nil Coalescingと呼ばれる書き方が便利です。
var text: String = "hello"
...
label.stringValue = text ?? "hi"
optionalがnilなら ?? の右側が使われます。
三項演算子で書くよりわかりやすくて楽ですね。
var text: String = "hello"
...
label.stringValue = text ?? "hi"
optionalがnilなら ?? の右側が使われます。
三項演算子で書くよりわかりやすくて楽ですね。
optionalをunwrap
optionalを定義したら使うときはきっちりunwrapする必要があります。
定義は
let text: String? = "hello"
と?をつければOKですが、unwrapには複数の方法があるようです。
optional chaining
text?.uppercased()
としたときに、うっかりtextがnilでもuppercased()は実行されないようです。
アプリが落ちにくくなるという最低限の安全策にはなりそうですが、エラー処理はきっちり行いたいですね。
forced unwrapping
text!というふうに!をつけると強制的にunwrapできます。
が、nilでないことを保証するためoptionalを使っているわけで、思想的には極力使用を避けたいところ。
optional binding
if let str = text {
...
}
とするとunwrapした結果nilでない場合のみifのスコープが実行されます。
guard構文もそうですが、optionalは?や!でunwrapのイメージが強いので、間違ってつけないように気をつける必要があります。
間違ってもコンパイルエラーになるので気づきますが。
simplicity unwrapped optional
let text: String! = "hello"
と?の代わりに!をつけるだけ。
textと書くだけで自動的に強制unrwapされます。
(要するに明示的なunwrapのシンタックスシュガー。)
当然nilに触っちゃう可能性があるので多用は厳禁、というか基本的に使いたくないですね。
使いどころとしてはXcodeが生成するIBOutlet。
UIパーツにアクセスするのに毎回unwrapは面倒&nilでないことは仕組み側で保証されているので、こういった限られたケースでは便利そうです。
定義は
let text: String? = "hello"
と?をつければOKですが、unwrapには複数の方法があるようです。
optional chaining
text?.uppercased()
としたときに、うっかりtextがnilでもuppercased()は実行されないようです。
アプリが落ちにくくなるという最低限の安全策にはなりそうですが、エラー処理はきっちり行いたいですね。
forced unwrapping
text!というふうに!をつけると強制的にunwrapできます。
が、nilでないことを保証するためoptionalを使っているわけで、思想的には極力使用を避けたいところ。
optional binding
if let str = text {
...
}
とするとunwrapした結果nilでない場合のみifのスコープが実行されます。
guard構文もそうですが、optionalは?や!でunwrapのイメージが強いので、間違ってつけないように気をつける必要があります。
間違ってもコンパイルエラーになるので気づきますが。
simplicity unwrapped optional
let text: String! = "hello"
と?の代わりに!をつけるだけ。
textと書くだけで自動的に強制unrwapされます。
(要するに明示的なunwrapのシンタックスシュガー。)
当然nilに触っちゃう可能性があるので多用は厳禁、というか基本的に使いたくないですね。
使いどころとしてはXcodeが生成するIBOutlet。
UIパーツにアクセスするのに毎回unwrapは面倒&nilでないことは仕組み側で保証されているので、こういった限られたケースでは便利そうです。
guardでガード
optionalとセットで使うと最大限の効果を発揮するguard構文によるnilチェック。
Cで書くとnullチェックのif文になる箇所がことごとくguardでガードされていきます。
たくさんguardが並んでいるコードを見るにつけ、nilチェックの頻度の高さを実感しますね。
varとlet
変数はvar、定数はletと単純明解な使い分けですが、うっかり定数もvarにしてしまうのはありがち。
Swift 3のコンパイラは親切で、値の変更がないvarはletにするよう教えてくれます。
コードレビューで指摘されがちなポイントをあらかじめ潰せるあたり、よく考えられていますね。
強力な型推定
スクリプト系の言語ではメジャーな型推定。
C++11でも対応してきているように、組み込み系にも着実に浸透しつつあります。
が、やっぱり最初はきっちり理解したいのでしばらくは省略せずに書いておくことにします。
var count: Int = 0
でも、慣れてくると、やっぱり型省略によるコーディングが楽そうです。
Swiftといえば!?
初めてSwiftで書かれたコードを見た時の印象は!?!?
とにかく?や!がたくさん出てきて面食らいました。
optionalという機能のようで、C言語でよくやるnullチェックを言語レベルで対応してくれている、と考えると分かりやすそうですね。
様々なレベルの膨大な開発者が関わっているiOSアプリを実装するための言語ならではでしょうか。
ソースコード管理はローカルなgitで
Xcodeでは普通にプロジェクトを作成するとローカルのgitリポジトリを作成してくれます。
一人で開発しているだけなので、一通りの実装ができるまではmasterブランチに直接commitしちゃうことにします。
何かやらかした時にリバートできればとりあえず良いので。
メニューからcommitを選ぶと変更したファイルや追加したファイルが自動的にadd候補になるので、適当にコメント書いてcommitするお手軽さ。
commitボタンがTouch Barに表示されるのが地味ながら便利ですね。
こういう絶妙な使い方を見つけていきたいものです。
落ち着いたらbranch切って拡張していく予定です。
Xcodeインストールとお試し実装
Xcodeのインストールは簡単。
Mac App Storeからボタン一つでセットアップが完了します。
早速起動して、Cocoaアプリを作り始めてみます。
Intrrface Builderが統合されてStoryboardという仕組みで画面遷移やUI設計ができるようになってますね。
適当にパーツを配置して「コントロール+ドラッグ」でコードと紐付ける作業は昔から変わっていないようです。
試しに「ボタンを押すとラベルにhelloと表示するアプリ」を作ってみます。
Storyboardにボタンとラベルを配置
次にコードからラベルを変更できるようにIBOutletとラベルを接続します。
StoryboardとView Controllerのコードを並べて表示し、コントロール+ドラッグすると、自動的にIBOutletが生成されるようになってます。
ボタンを押した時に呼ばれるIBActionも同様に接続。
最後に唯一のコーディング。
View Controllerに生成されたIBActionからIBOutletに接続したラベルに値を設定します。
label.stringValue = "hello"
Mac App Storeからボタン一つでセットアップが完了します。
早速起動して、Cocoaアプリを作り始めてみます。
Intrrface Builderが統合されてStoryboardという仕組みで画面遷移やUI設計ができるようになってますね。
適当にパーツを配置して「コントロール+ドラッグ」でコードと紐付ける作業は昔から変わっていないようです。
試しに「ボタンを押すとラベルにhelloと表示するアプリ」を作ってみます。
Storyboardにボタンとラベルを配置
次にコードからラベルを変更できるようにIBOutletとラベルを接続します。
StoryboardとView Controllerのコードを並べて表示し、コントロール+ドラッグすると、自動的にIBOutletが生成されるようになってます。
ボタンを押した時に呼ばれるIBActionも同様に接続。
最後に唯一のコーディング。
View Controllerに生成されたIBActionからIBOutletに接続したラベルに値を設定します。
label.stringValue = "hello"
初アプリ開発 やること一覧
さあ作ろう!と思ってもそんなにすぐには始められないアプリ開発。
やらないといけないことをざっと挙げてみると。。。
Xcode 8のセットアップ
プロジェクトの作成とコード管理
Swift 3理解
Cocoa/Foundationフレームワーク理解
アプリの要件洗い出し
設計
実装
テスト
アイコン作成
リリース などなど
個人の趣味で作るアプリなので、どこまできっちりやるかは気分次第ですが、やらないといけないことは山積みです。
命名 Touchdown
作るものが決まったら最初に悩むのはアプリ名。
〇〇 Converter 的なストレートな名前にするか、もうちょっとひねるかでいろいろ思案を巡らせます。
やりたいことは、
などなど。
悩んだ結果、 Touchdown と命名しました。
飛行機の着陸や、アメフトで点を獲得するTouchdownです。
Touch BarにあやかってTouchという単語を入れたかったのと、パッと変換できる爽快感や、Swiftをちゃんと理解して地に足つけたいという意図を込めたつもりです。
名前は決まったのでちゃんと使えるレベルまでは作り上げたいですね。
〇〇 Converter 的なストレートな名前にするか、もうちょっとひねるかでいろいろ思案を巡らせます。
やりたいことは、
- Touch Barでワンタッチ変換
- ファイルサイズや画像サイズを縮小
- SwiftでMacのアプリを作れるようになること
などなど。
悩んだ結果、 Touchdown と命名しました。
飛行機の着陸や、アメフトで点を獲得するTouchdownです。
Touch BarにあやかってTouchという単語を入れたかったのと、パッと変換できる爽快感や、Swiftをちゃんと理解して地に足つけたいという意図を込めたつもりです。
名前は決まったのでちゃんと使えるレベルまでは作り上げたいですね。
Touch Barを活用できるアプリを検討
Touch Bar対応アプリを作ってMacBook Pro 2016をフル活用したい!というモチベーションはあっても、何を作るか決めないと始まりません。
日常使いで役に立つものをいろいろ考えて至った結論は「画像変換ツール」。
PNGで撮影したスクリーンショットをJPEGに変換したり、大きな画像を縮小したりと、Blogなどで使う画像を変換する機会が非常に多いので、Touch Barで楽できるようにしたいですね。
想定する使い方はこんな感じ。
実は過去に同じようなアプリを作っていたのですが、今風に作り直してみることにします。
日常使いで役に立つものをいろいろ考えて至った結論は「画像変換ツール」。
PNGで撮影したスクリーンショットをJPEGに変換したり、大きな画像を縮小したりと、Blogなどで使う画像を変換する機会が非常に多いので、Touch Barで楽できるようにしたいですね。
想定する使い方はこんな感じ。
- 画像ファイルを変換ツールにドラッグ&ドロップ
- Touch Barでプリセットを選択
- 変換完了
実は過去に同じようなアプリを作っていたのですが、今風に作り直してみることにします。
Swift独学開始!
MacBook Pro 2016のTouchbarを活用すべく、11年ぶりにMacのアプリを作ってみることにしました。
当時はCocoa + Objective-Cという開発環境。
Cocoa Bindingが出始めた頃にやめてしまったので、最近のMacの開発環境に追いつくのが最優先。
せっかく今から始めるのでSwift 3を独学で勉強していきます。
こちらのサイトではアプリを作りながらわかりにくかったポイントなどをメモ的に記録していこうと思います。
当時はCocoa + Objective-Cという開発環境。
Cocoa Bindingが出始めた頃にやめてしまったので、最近のMacの開発環境に追いつくのが最優先。
せっかく今から始めるのでSwift 3を独学で勉強していきます。
こちらのサイトではアプリを作りながらわかりにくかったポイントなどをメモ的に記録していこうと思います。
登録:
投稿 (Atom)
Touchdown 1.3公開!
Touchdown 1.3を公開しました。 ダウンロードは こちら から。 Ver.1.3の変更点は以下の通りです。 新機能 macOS Mojave 10.13以降に対応 ダークモードに対応 ぜひお試しください。
-
Touchdownでは複数の設定を保存して切り替えられるようにするため、NSTableViewをUIとして使います。 基本的なとこだけど、何かと分かりにくいCocoa Binding。 iOSでは使えないらしく情報も少なめなので、簡単にまとめておきます。 S...
-
TouchdownをSwift 4に変換した際に、フレームワーク側の変更に伴うコードの置換が発生していました。 ファイルのドラッグ&ドロップに関する部分ではドラッグ受付登録が、 register(forDraggedTypes: [NSFilenamesPboard...
-
macOS High SierraになりSwiftも4にバージョンアップ。 TouchdownもSwift 3からSwift 4に移行します。 基本的にはXcodeの移行ツールに従うだけで、途中で聞かれる Minimize Inference Match Swift...