ぴよログ

↓に移転したのでこっちは更新されません、多分。

Mac: Logicoolマウス Shift+ホイールでの横スクロールが遅い問題が解決した

OSX Marvericksの頃だったと記憶しているが、Logicoolのマウス(というかトラックボール)でのShiftキーを押しながらの横スクロールが1pxずつぐらいしか動いてないんじゃないかと思うほど遅くなってしまい、全く使い物にならなくなってしまった。仕方ないので横スクロールだけはトラックパッドを2本指でなぞるという不毛なことを長いこと続けていたが、この問題が先ほど解決した。

このRedditの投稿によると問題の原因はドライバであるLogicool Control Centerだったようだ。

Maverick osx "Shift scrolling (horizontal scrolling) very slow. Any fix? : mac

実際にLogicool Control Centerをアンインストールしたら横スクロールが遅い問題は解決していた。なお、アンインストールにはLCC Uninstallerを実行する必要がある。これはアプリケーション→ユーティリティ以下にある。

ところで、ドライバを削除してしまうことでせっかくのマウスの機能を活かせなくなってしまうのは勿体ない。同じRedditのスレッドでsteermouseという汎用のドライバソフトが挙がっていおり、それを使えばある程度は対処できるみたい。

ステアーマウス

有償のソフトだけど30日間の無料トライアル期間がある。マウスについているボタン4・ボタン5でブラウザの戻る・進むができるなど、公式のドライバでできていたことは問題なくできているような気がするので、このまま使っていける気がしている。

横スクロールが遅い問題はFinderのカラム表示や、XcodeでのStoryboardを編集する際にとても困っていたので対処できて本当に嬉しい。

LOGICOOL ワイヤレストラックボール M570t

LOGICOOL ワイヤレストラックボール M570t

AFHTTPSessionManagerでHTTPBodyを設定する

AFNetworkingのAFHTTPSessionManagerを使っていてBodyに素のXMLを入れてPOSTしたかったのだが、そのやり方がなかなか見当たらずソースコードを読んで解決したのでメモしておく。

ざっくり言うとAFHTTPSessionManagerでのPOSTはこんな感じに行う。

let manager = AFHTTPSessionManager()
manager.POST(url, parameters: params, success: { (task, response) -> Void in
    // success
}) { (task, error) -> Void in
    // failure
}

POSTするパラメータがあれば、上のメソッドparametersのところにdictionaryとかで渡せばいいはずだが(確か)、今回やりたいのはBodyにXMLのテキストを入れるということだった。

まず気軽にparametersにXML文字列を直接渡してみようなどとやってみたら失敗した。

let manager = AFHTTPSessionManager()
let xmlstr = "<?xml ..."
manager.POST(url, parameters: xmlstr, success: { (task, response) -> Void in
    // success
}) { (task, error) -> Void in
    // failure
}

これでできたリクエストを見てみるとBodyにはこんなのが入っていた。

(null)=%3C%3Fxml%20...

どうやら名前が空で値がURLエンコード済文字列のパラメータをBodyとして設定してしまっているらしい。

ソースコードを読むとそれがデフォルトの動作だということがわかった。

// AFURLRequestSerialization.m
switch (self.queryStringSerializationStyle) {
    case AFHTTPRequestQueryStringDefaultStyle:
        query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding);
        break;
}

じゃあデフォルトじゃない動作は、というと上のコードの近くにあった。

// AFURLRequestSerialization.m
if (self.queryStringSerialization) {
    NSError *serializationError;
    query = self.queryStringSerialization(request, parameters, &serializationError);

    if (serializationError) {
        if (error) {
            *error = serializationError;
        }

        return nil;
    }
}

self.queryStringSerializationはBlockで、このブロックを設定してparameterのシリアライズをカスタマイズできるよ、ということのようだった。

つまり文字列をそのままBodyに入れたい場合は、

let manager = AFHTTPSessionManager()
let xmlstr = "<?xml ..."

manager.requestSerializer.setQueryStringSerializationWithBlock { (request, params, error) -> String! in
    return params as! String
}

manager.POST(url, parameters: xmlstr, success: { (task, response) -> Void in
    // success
}) { (task, error) -> Void in
    // failure
}

manager.requestSerializer.setQueryStringSerializationWithBlockでparameterをそのまま文字列と返してあげればよかった。

これで解決できた。良かった。

Webエンジニアの教科書

Webエンジニアの教科書

NSTextFieldで入力を検出する

iOS開発からCocoaにも手を付け始めた人間によくあることだろうが、UIKitとAppKitでお作法が同じだったり微妙に違ったりすることでちょくちょくつまずくことがある。

今日はテキストフィールドでユーザーが入力を行うたびに入力値のチェックをしてOKボタンの有効/無効を切り替えるという処理を書こうとしてつまづいた。

UITextFieldの場合

UITextFieldの場合はStoryboardからCtrl+ドラッグでActionを作るときにEditing Changedイベントを選んであげればOK。

NSTextFieldの場合

Storyboardを使って同じようなことをやろうとしてもiOSのようにコントロールに発生したイベントに対応したActionを作ることができない。ここまで書いて気がついたけどAppKitはイベント型(?)じゃないということなんだね。

ではどうすればいいかといえば、Delegateが存在するのでそれを実装してあげれば良い。DelegateiOS開発でも毎回使うだろうからiOS Developerなら大丈夫のはず。

class ViewController: NSViewController, NSTextFieldDelegate {
    @IBOutlet weak var textField: NSTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do view setup here.
        textField.delegate = self
    }

    override func controlTextDidChange(obj: NSNotification) {
        doSomething()
    }
}

できた。

Cocoaアプリケーション(OSXアプリ)はじめました

実験的な意味も含めてCocoaアプリの開発を始めた。完成したものはMac App Storeに並べてもらうべくiOSと同じように申請して審査してもらっている。

そもそもMac App Storeに並んでいるアプリはなんと呼ぶのが正解なのかすらよくわかっていないが、Cocoaアプリということにしておく。

iOSの開発は5年ぐらいやっているけれど、Cocoaアプリの開発は全くの初めてだったから調べながら進めている。今は単機能のSingle Windowアプリを3つ作った程度で、ドキュメントベースのアプリケーションのような複雑なことはしていないが、Cocoaアプリ開発についてのコメントというか感想を書いてみようと思う。ただの感想文ですよ。

情報が少ない、見つけにくい

まず言えるのはこれだと思う。iOS開発の情報はネットの世界に溢れている。日本語でも大量の情報があるから大抵のことは調べればできる。

その反面、Cocoaアプリケーションの情報はなかなか見つからない。日本語で書かれたそこそこ新しい情報がたまにヒットするが、大抵はAppleのドキュメントページが検索のトップに来る。そしてその情報も数年前に書かれたものだったりする(必ずしも情報が古いというわけではなく、SDKが変わっていないというだけだったりするが)。

検索自体もしづらい。例えばAVFoundation OSX Swift ○○とか調べようとするとiOSの検索結果がずらりと出てくる。iOSとは独立してはっきりしたネーミングがあればよかったのにと思う。

最近気がついたのは、Cocoaというキーワードを入れておくとなかなか良さそうな検索結果を得られる気がする。

お作法が割と違う

UI周りがまあまあ違う。iOSのように画面サイズを制限されない分、自由にビューを配置したり複数のウィンドウを使ったり色々なことができる。

例えばiOSではUIImageを使っているところをCocoaではNSImageを使うことになるんだけど、このクラスのインターフェースが微妙に異なるので同じように使えるもんだと思っているといちいち躓く。

で、ドキュメントを探したり例を探そうとすると最初の話「情報が見つからない」に行き着いて時間がかかる。

慣れるまでは同じことを何度も調べる可能性があるので見つけたページはEvernoteのWebClipすることにしている。この機能は最近全く使っていなかったけれど久しぶりに重宝しそうだ。

iOSの経験はかなり活きる

お作法が微妙に異なるとは言え、SDKの思想はiOSとほぼ変わらないからiOS経験者は比較的すんなり入ることができる。

例えば、縦横の表を表現するときにはNSTableViewを使うが、iOSの経験のない人がいきなりこのViewを使おうとしたら混乱するんじゃないかと思う。予想です。

iOSUITableViewもこのNSTableViewdelegatedataSourceを用いてコンテンツや挙動を制御するわけだけど、行と列がある分NSTableViewのほうが複雑と言っていいと思う。先に複雑じゃないUITableViewを触っていれば、比較的すんなり入れるんじゃないかな。

Cocoaアプリケーション開発でもStoryBoardやSegueのようなUI開発の仕組みがあるし、Cocoapodsなども活用できるからiOS経験者にはハードル低いんじゃないかな。みんなやったほうがいいよ。

おわり

これからは備忘録的にちょくちょく書いていく予定。

ちなみに最初に作ったのはiPhoneiPadのAppStoreで使えるPreview動画を自動生成するアプリ。ネーミングやデザインなどはお察しだけどなかなか便利なのができたよ。

Movie Resize for App Previews
カテゴリ: ユーティリティ, グラフィック&デザイン

MAC OS X COCOAプログラミング 第4版

MAC OS X COCOAプログラミング 第4版

Single WindowなOSX Appで全てのWindowが閉じたときの挙動でリジェクト

MacのアプリケーションはWindowを赤ボタンとか⌘Wで閉じてもアプリケーション自体が終了しないことが多いが、その場合メインウィンドウを再表示できるようにしておかないとMac App Storeに申請した際にリジェクトを食らってしまう(食らった)。

1つめの対応方法として、Dockアイコンをクリックしたときにメインウィンドウを再表示するコードをAppDelegateに書く。

    func applicationShouldHandleReopen(sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        if flag == false && sender.windows.count > 0 {
            sender.windows[0].makeKeyAndOrderFront(self)
        }
        return true
    }

もしくは全てのウィンドウが閉じたときアプリケーションを終了させてしまうというのもOKらしい。これは推測だけど、申請したのがシングルウィンドウなアプリケーションだったからこの対処方法でもいいよと言ってくれたんじゃないかと思う。ドキュメントベースの場合はウィンドウが閉じることって頻繁にあるだろうから多分だめなんじゃないかな。

その場合はAppDelegateにこんなコードを書く。

    func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
        return true
    }

今回は単機能のアプリだったので後者で対応することにしてみた。

MAC OS X COCOAプログラミング 第4版

MAC OS X COCOAプログラミング 第4版

【追記あり】デフォルトアプリケーションがXcode-betaになってしまったのを戻した

XCodeのベータ版を入れたらxcodeprojなどを開くときのデフォルトアプリケーションがベータ版のほうになってしまった。いやいや君普段はそんなに使わないから。

僕はターミナルでそのプロジェクトのGitディレクトリに移動して、そこからopen hogehoge.xcodeprojとかやることが多いのでデフォルトアプリケーションは重要なのだ。

デフォルトアプリケーションの変更はFinderの情報を見るあたりからできるというのは知っていたが、ついでに*.hなど、基本XCodeで開くよねとなっているファイルたちのデフォルトも変えたかったからそれだと物足りなかった。全部のファイルタイプに同じことするの面倒なので。

そこで強引な方法で解決することにした。

  1. XCode-beta.appを削除
  2. ゴミ箱から戻す

これでOK!

追記

だめだったー。ベータを起動したら元に戻ってました。なんとかならんのこれ。

UIWebView内リンクでの遷移をNavigationでやるやつ、Swiftバージョン

iOSでUIWebView内のリンクをタップしたらNavigationで画面遷移させる実験 - PILOG

↑こんなん書いたけどこのときはObjective-Cで書いてたのでSwiftで書き換えてみました。オプショナルが全然慣れませんがこれから勉強します。

import UIKit
import TKRSegueOptions

class ViewController: UIViewController, UIWebViewDelegate {
    
    let HOST = "http://localhost:4000"

    @IBOutlet weak var webView: UIWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        webView.delegate = self
        var urlstring = HOST + "/"
        if let path = self.segueOptions?["path"] as? String {
            urlstring = HOST + path
        }
        let url = NSURL(string: urlstring)
        let req = NSURLRequest(URL: url!)
        webView.loadRequest(req)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        
        if(request.URL?.scheme == "callback"){
            println(request.URL?.absoluteString)
            let urlstring = request.URL?.absoluteString
            let href = urlstring?.substringFromIndex(advance(urlstring!.startIndex, 11))
            performSegueWithIdentifier("next", options: ["path": href!])
        }
        return true
    }
}

詳解 Swift

詳解 Swift