2016年12月3日土曜日

LandscapeでimagePickerがうまく表示されない

PortraitとLandscapeの両方に対応したアプリ作ってる。
写真ライブラリから選ぶためのUIImagePickerControllerが、公式にはPortrait表示にしか対応していないらしく、Landscapeで表示しようとすると強制的にPortrait表示になってしまう。(iPhoneだけ。iPadではうまくいく)
また、プロジェクトの設定でLandscape以外許可しない状態で表示しようとすると、クラッシュしてしまう。
こういうアプリで
こうしたいのに

こうなっちゃう!?
今まで気にしたことなんかなかったけど、LandscapeのimagePickerって普通になかったっけ? iPadだけでiPhone持ってないから知らなかっただけ?

UIImagePickerControllerのAPIリファレンスには以下のように書かれている。

Important
The UIImagePickerController class supports portrait mode only. This class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified, with one exception. You can assign a custom view to the cameraOverlayView property and use that view to present additional information or manage the interactions between the camera interface and your code.

和訳(by おいら)
重要

UIImagePickerControllerクラスは、縦画面モードだけをサポートします。 このクラスは現状のままで使われることを目的として、サブクラスとすることをサポートしません。 このクラスのためのビューの階層はprivateで、例外的に修正されてはいけません。 あなたはカスタムビューをcameraOverlayViewのプロパティに割り当てることができて、追加情報を提示するか、カメラインターフェースとコードの間の相互作用を管理するために、そのビューを使うことができます。


どうやらUIImagePickerControllerをサブクラスにしたカスタムクラスを作ることも禁止されているらしい。禁を破って作ると、リジェクトされるという噂もあるらしい。
でもネット情報だとカスタムクラス作ったり、overrideして実現しちゃってるみたい。

ViewController.swift あたりのclassの外に以下を書いておけばいい。
extensionは既存クラスを書き換えたり、繼承なしで機能を拡張するためのもの。

extension UIImagePickerController
{
    override open var supportedInterfaceOrientations:UIInterfaceOrientationMask {
        return .all
    }
}


ちなみにSwift2.xでは以下の書き方だった。
extension UIImagePickerController
{
    public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return .All
    }
}

Swift3.0からsupportedInterfaceOrientationsの定義が変更になったらしく、override open var〜で書かないと
method does not override any method from its superclass
なんてエラーが出ちゃう。
関数だったものが変数になったのね? なんだかよくわかんないけど。

無事に表示できたけど、このことが理由でリジェクトされないといいけどな。まあ海外のデベロッパー含めてやってるみたいだから大丈夫なのでは?
そもそもなんでLandscape表示もできるのに、Portrait表示しか対応してないって言ってるんだろうね?

参考

2016年10月26日水曜日

特別編:消えたユーザ辞書を復活

プログラミングと関係ないけど、Mac、iOS関係なのでこちらに書いておく。

macOS Sierraにしたら、ことえり(今もことえりって呼んでいいの?)のユーザ辞書が消えやがりましたよ。
以前のOSでもこういう不具合に悩まされてた人がいたみたいだけど、自分はこれが初めて。
iCloudドライブ経由で同期されるため、iPadに残ってればそこからMacにも反映させられるって情報もあったんだけど、その通りやってもiPadの方のが逆に消えてしまってお手上げだった。

今回、運よくTime Machineでバックアップが取ってあったので、それを使って復活させたわけ。

場所はユーザ/ユーザ名/ライブラリ/Dictionaries。
そこにあるユーザ辞書とUserDictionaryDescription.plistの二つをTime Machineで復活させる。

そしてシステム環境設定のキーボード/入力ソース/日本語の一番下にある追加辞書:に上記のユーザ辞書をドロップすればいい。
念のためにMacを再起動したけどね。
とりあえずうまくいってくれて助かった。

つーかさ、Sierra 10.12.0にアップデートして消えて、ちょこちょこ自分で辞書登録始めてたんだけど、10.12.1に上げたらまた消えたんだよね。
ユーザ辞書のトラブルはずいぶん昔からあるんだから、Appleもなんとかせいや。

ユーザ辞書の書き出し

ユーザ辞書を書き出す場合も、上記を任意のフォルダにドラッグ&ドロップすればいい。その際、なぜか辞書はコピーされずに移動になってしまうので注意。
編集した後にまた戻してやらないとユーザ辞書が消えてしまうことになる。

書き出されたユーザ辞書はテキストファイルになるので、それを開いて編集し、また戻してやればいい。

iCloudとの同期

上でも説明したライブラリのDictionariesフォルダ。
これのCoreDataUbiquitySupportフォルダを消して、iCloudを有効にしたままMacを再起動すると、iCloudからユーザ辞書がダウンロードされて復活するようだ。
一応うまくいったが、iPadの方で登録したのとまた違うようだ。
正直わからん。

2016年10月20日木曜日

コードで作ったオブジェクトが動かない時はARCを疑え

Storyboardで配置したUI部品(ボタンとかラベルとか)じゃなく、コードの中で作った部品が機能せず、「なんでじゃー!」と発狂したことなら皆さん何回かあると思います。

これって、本来はプロパティ(インスタンス変数)に保存しておかなけりゃいけないものを、関数の中だけのローカル変数に保存したばっかりに、関数を抜けた途端にARCが働いてガベージコレクションされちゃうからなのよね。
(実際にガベージコレクションするタイミングはARC任せだけど)

エラーやウォーニングが出るわけじゃないし、部品自体は画面に表示されたままだから、なんでじゃー! ってなるんだけど、iOSの仕組みを考えるとなるほどということになる。

UI部品ばかりじゃなく、音を鳴らすとかの場合にも、関数の中でサウンド関係のインスタンスを作って鳴らしたはずが鳴らなかったりするのは、やはり鳴り終えるまでガベージコレクションされない変数(要するにプロパティ)として保持しておかなきゃいけないのよね。

Storyboardで作ると自動的にプロパティが作られるので実感わかないけど、要はそういうこと。

2016年9月26日月曜日

UIImagePickerControllerは自分で閉じる

フォトライブラリから画像を取得するコードをSwift 3.0で書いてるんだけど、うまくいかなかった。

Delegateの設定

if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
     let imagePicker = UIImagePickerController()
     imagePicker.delegate = self
     imagePicker.sourceType = .photoLibrary
     present(imagePicker, animated: true, completion: nil//イメージピッカー表示
else {
     print("フォトライブラリが使えません")
}

上記で
imagePicker.delegate = self
と宣言するために、頭のところでUIImagePickerControllerDelegateと設定するだけじゃダメで、以下のようにUINavigationControllerDelegateも設定してやらないとエラーが出てしまう。

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { ~ }

UIImagePickerControllerのviewは自分で閉じよう

イメージピッカーで画像を選択したら、以下のdelegateメソッドが呼ばれるわけだが、
[Generic] Creating an image format with an unknown type is an error
というメッセージが出るばかりでイメージピッカーが閉じて画像をimageViewに表示してくれない。
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    let image = info[UIImagePickerControllerOriginalImage] as! UIImage?
    imageView.image = image
}

と思ったら、
picker.dismiss(animated: true, completion: nil)
ってやって自分でイメージピッカーを閉じてやらにゃいかんのね。
今までObjective-Cでやってた時は自分で閉じてくれてたのに。
なんだろ、複数選択とかに対応したとかかね?
なおピッカーが閉じたところ、隠れてたimageViewには選択した画像がちゃんと表示されてた。
それでも[Generic] Creating an image format with an unknown type is an errorのメッセージは出るんだけど、これはXcode8になってから余計なメッセージが出る現象の一つかもしれないので、実用上問題なければ無視すればいいと思う。

2016年9月21日水曜日

Swift3を使わない方法

Xcode8からデフォルトの言語がSwift3.0になったけど、いろいろ大きく変わるようで資料も少ないし、コンバーターかけても動かないコードが続出したり。

いずれ3.0を覚えなければいけないにしても、とりあえず今までのSwiftで書くやり方があった。

プロジェクトのTARGETS、Build Settings、Swift Compiler - Version、Use Legacy Swift Compiler VersionをYESにしてやるだけ。
これでSwift3を回避できる。
Xcode8以前は2.2だったけど、2.3に上がってはいると思う。

Swift 3 DEATHよ

Xcode8でSwift 3になったので、そっちへの気づいた変更点を書いていくですよ。
コードのトランスレーターダとSwift 2.4だかも選べるけど、3にしない理由はないと思うので、3のだけ書きますよ。

ざっと見たけど、いろいろ変わりやがったな。
全てをチェックしてらんない。
関数の引数にラベルを付ける方向で、NSのサフィックスを取る方向で、関数名を短くしたり、関数名と引数のラベルをセットで意味を持たせたり…
書くのが楽になったり、わかりやすくなったりしてるのかどうかわかんないけど、ちょこちょこ変えないでほしいよね…

今までのアップデートだと、トランスレーターでちょっとした変更点は直してくれたけど、今回トランスレーターかけてもエラーになるところが山のように出たよ!!
何のためのトランスレーターだよ、この野郎!!
みなさん覚悟しましょう(≧∇≦)

indexPathのプロパティをいじる時は as NSIndexPathとしないといけない

let row = indexPath.row - alienPicsKeysArray.count

let row = (indexPath as NSIndexPath).row - alienPicsKeysArray.count

関数の第1引数のラベルがない場合、_ を付ける?

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

UIColorの指定がcolor()がいらなくなった


vc.bgSkyImageView.backgroundColor = UIColor.clearColor()

vc.bgSkyImageView.backgroundColor = UIColor.clear

列挙型の指定が先頭小文字になった

UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)

UIView.setAnimationCurve(UIViewAnimationCurve.easeInOut)

Booleanのプロパティがis〜になった

vc.talkView.hidden = true
vc.cameraBtn.enabled = true

vc.talkView.isHidden = true
vc.cameraBtn.isEnabled = true

NSTimerがただのTimerに

どうやら全体的にNSのprefixがいらなくなったようだ。
var urouroTimer = NSTimer()

var urouroTimer = Timer()

関数名が変わった

finishTimer = NSTimer.scheduledTimerWithTimeInterval(rnd,
                    target: self,
                    selector: #selector(Cennina.finish),
                    userInfo: nil,
                    repeats: false)
            }


finishTimer = Timer.scheduledTimer(timeInterval: rnd,

                    target: self,

                    selector: #selector(Cennina.finish),
                    userInfo: nil,
                    repeats: false)
            }


CGRectMakeとかが CGRectとかに

CGPointMakeなんかもそう。
ラベルも必ずつくようになったのね。
vc.ufoImage.frame = CGRectMake(0, 0, 200 * vc.倍率, 200 * vc.倍率)

vc.ufoImage.frame = CGRect(x: 0, y: 0, width: 200 * vc.倍率, height: 200 * vc.倍率)

レイヤーのaddAnimationがaddに

vc.view.layer.addAnimation(transition, forKey: nil)

vc.view.layer.add(transition, forKey: nil)


Switch文の書き方

optionalな値を判断するcaseの値の前に?が。めんどくせえ!
switch month {
        case 1: //1
            msgJanuary(day)
        case 2:
            msgFebruary(day)
        default:
            print()
        }

switch month {
        case ?1: //1
            msgJanuary(day!)
        case ?2:
            msgFebruary(day!)
        default:
            print()
        }

UserDefaultsの書き方

setする値が何でもsetだけで良くなったのだね。
読む時は型によって.object とか .boolとか指定しないといけない。
let ud = NSUserDefaults.standardUserDefaults()
if let _ = ud.objectForKey("currentStage")
ud.setBool(soundFlag, forKey: "soundFlag")
ud.setObject(currentStage.rawValue, forKey: "currentStage")

let test = ud.objectForKey("currentStage")

let ud = UserDefaults.standard
if let _ = ud.object(forKey: "currentStage")

ud.set(soundFlag, forKey: "soundFlag")


ud.set(currentStage.rawValue, forKey: "currentStage")

let test = ud.object(forKey: "currentStage")

Info.plistをローカライズする

注意:結論から言うとうまくいきませんでした。

iOS10、Xcode8からプライバシー関連の設定が増え、例えば画像ライブラリにアクセスするコードを書く場合に、あらかじめInfo.plistの必要な項目にユーザーに伝える使用目的を明示するメッセージを書かなければいけなくなった。
(書いたメッセージがアクセス時にアラートで表示される)

ま、そりゃいいんだけど、そこに日本語用、英語用なんて項目はないんで、Info.plist自体をローカライズして日本語用、英語用を作ることになる。
やり方としてはInfo.plistを選んで、右側のインスペクタからLocalize...ボタンを押して云々なのでまあわかるだろう。

でもそれやってビルドしようとしたら、
error: could not read data from 'パス/アプリ名-Info.plist': The file "アプリ名-Info.plist" couldn't be opened because there is no such file.
というエラーが出てビルドできなかった。
メッセージの内容としてはInfo.plistが見つかんないから開けねぇぞってことだけど、ちゃんとplistファイルはある。

調べたところ、Xcodeの以前からのバグで、パスがおかしいんだそうな。
プロジェクトのパッケージ内容を開いてproject.pbxprojを開き、中の該当部分を絶対パスから相対パスに書き直すといいとあったんで見たけど、どうも最初から相対パスになってるっぽい。(参考リンクもう一つ)
一度Localizeのチェックを外し、再度チェックすると直るとか書いてあったんだけど、試してもダメ。

それ以外の情報もないようなので、メッセージは英語と日本語両方入れておく。ダサダサ〜。
やむを得ずこうなった
後で解決方法がわかったら直します。
こんなとこで止まってリリースを遅らしちゃなんねぇ!!