2018年4月20日金曜日

新しいiPhoneの登録

実機確認したい!

とうとうねiPhone買ったんですけど、実機確認のための登録ってどうやるんだっけ? ってことになったんですよ。
iPad2からiPad Air2にした時は特に何もしなかったような気がしたんですけど、iPhoneではちゃんとApple Developerサイトで登録してやらんといかんようなんですよ。

Developerサイトにログインし、AccountタブのCertificates, Identifiers & Profilesを開き、DecicesからiPhoneを選び、右上の「+」ボタンを押すと、NameとIdentifierを入力する画面になるわけですね。
そこで、XcodeのWindowメニューにある「Windows & Simulators」を選ぶと出てくる画面で、デバイス名とIdentifierをそれぞれNameとUUIDに入れてやって下の方にあるボタンを押してやるといいのですよ。
できたからいいけどさ、こういうのもXcodeで統一的に扱えるようにならんもんかね? いちいちApple DeveloperサイトやiTunes Connectのサイトを行ったり来たりじゃめんどくさいよね。

Outlet、Actionの接続ができない:Could not insert new outlet connection

問題

Outletが接続できなくなるエラーが発生。
Actionもダメみたい。


対処方法

以下のように自分で@IBOutletや@IBActionのコードを入力し、そこに対して接続すれば使える。

@IBOutlet weak var test: UIBarButtonItem!

根本的解決方法はあるのか?

ただし根本的な解決にはなっていないので、以下を試してみた。

プロダクトのClean

⌘+Shift+K

DerivedDataの削除

DerivedDataはプロダクトのビルドフォルダーにある中間ファイルで、 /ユーザ名/Library/Developer/Xcode/DerivedData/ にある。
Xcodeから ⌘+Option+Shift+K でその一部を削除できる。
よりちゃんと削除するにはDerivedDataフォルダ内の該当プロダクトのフォルダもしくは、DerivedDataフォルダごと削除してもいい。
プロダクトをXcodeで開けば再作成されるので問題はない。

Storyboardのリファレンス削除と再登録

Main.storyboardをRemove Referenceだけし、すぐにプロジェクトにドラッグで戻す
こちらのページ参照

接続先のファイルのリファレンス削除と再登録

たとえばViewController.swiftに接続できないなら、それを上記Storyboard同様にRemove Referenceだけしてまた戻す。
念のため、戻す前にXcode終了、DerivedDataを削除、Xcode再起動後にプロダクトcleanなどを行なった。

Xcode関係plistをいじる

/ユーザ名/Library/Preferences/com.apple.dt.Xcode.plist に対し、コマンドラインもしくはXcodeで開いて変更を加える。

その1

こちらのページ参照
plist中の IDEIndexDisable の値が1になってたら0に戻すというもの。
ユーザ名/Library/Cashces/com.apple.dt.Xcode と、 ユーザ名/Library/Saved Application State/com.apple.dt.Xcode.savedState の2ファイルをゴミ箱に捨てて「ゴミ箱を空にして」からじゃないと、値が元に戻ってしまいうまくいかなかったようだ。

その2

こちらのページ参照
その1に近いが、 com.apple.dt.Xcode を削除し、plistのDisable値を削除、Enable値を1に設定しているようだ。

ターミナルで以下3つを実行する。
rm -rf ~/Library/Caches/com.apple.dt.Xcode
defaults delete com.apple.dt.Xcode IDEIndexDisable  
defaults write com.apple.dt.Xcode IDEIndexEnable 1

しかしcom.apple.dt.Xcodeを削除した段階でIDEIndexDisableがすでにないらしく、
Domain (com.apple.dt.Xcode) not found.
Defaults have not been changed.
などと出たので、2番目はいらないのかも?

Target MembershipのチェックOFF/ON


ユーティリティエリアにあるTarget Membershipにアプリ名のところに✔が入ってるが、これをOFF → ONしてみる。
再度Index処理が開始される。

Xcodeの再インストール

Xcodeを削除し、AppStoreから再インストールしてみた。

XcodeとMacの再起動

そのまんま。

しかし、いずれをやってもダメだった。
人によってうまくいく場合、いかない場合があるようだ。
他のプロジェクトでは問題ないようなので、プロジェクトファイルのどこかが壊れちゃったんだろうね。

このままじゃ不便なので、今後も継続して調べる。わかったら追記する。

2018年4月18日水曜日

macOS/画像ファイルの保存

iOSもいいけど、macOSのアプリも作ってみたいというので勉強始めた。

CIFilterをかけた画像を保存するアプリ。
ファイル保存のウィンドウNSSavePanelを表示したいが、ネットで調べたコードじゃエラーが出て表示されない。

わかったのは、最近Sandboxという仕組みになり、そのせいでファイルの扱いがReadOnlyになっていたのだ。
TARGETSのCapabilities / App Sandbox / File Access / User Selected File のPermission & Accessを、Read Only → Read/Write に変更してやったら(App SandboxのボタンがOFFになる)NSSavePanelが表示され、画像の保存もできた。
めんどっちーね。

なお、User Selected FileがRead/Writeになっていれば、App SandboxのボタンをONにしても保存できるようだ。やっていいのかどうかしらんけど。

Sandboxとはなんぞや?

どうやら砂場を意味していて、子供をその中で遊ばせときゃ安全…という概念で、プログラムを保護された環境下で動作させるものだという。(マイナビニュースより)

公式には以下のように書かれている。
macOSのApp Sandboxは、アプリケーションが意図された動作だけを行うようにする機能です。アプリケーションのサンドボックス化は、アプリケーションをあなたのMacの重要なシステムコンポーネント、あなたのデータ、そしてほかのアプリケーションから分離させます。もしアプリケーションに悪意のあるソフトウェアが含まれていたとしても、サンドボックス化が自動的にそれをブロックするので、あなたのコンピュータと情報は守られます。macOSでは、内蔵されたPDFビューアや、Adobe Flash Player、Silverlight、QuickTime、Oracle Javaなどのプラグインをサンドボックス化することで、Safariにサンドボックス保護機能を持たせています。さらにmacOSは、メール、メッセージ、FaceTime、カレンダー、連絡先、写真、メモ、リマインダー、Photo Booth、クイックルックプレビュー、Game Center、辞書、Font Book、Mac App Storeなどのアプリケーションもサンドボックス化します。

コード(クロージャ内で保存する場合)

勉強のためにいろいろ余計なプロパティも設定してみたが、こんな感じ。
savePanelの表示と一緒に、保存処理するクロージャを設定する場合。

//画像保存
@IBAction func saveImage(_ sender: Any) {
    let saveImage = myImageView.image
    
    let savePanel = NSSavePanel()
    savePanel.title = "画像保存だってよ" //表示されない?
    savePanel.prompt = "保存" //デフォルトボタンのテキスト
    savePanel.nameFieldLabel = "名前:" //ファイル名入力欄の左に表示されるテキスト
    savePanel.nameFieldStringValue = "名称未設定" //デフォルトファイル名
    savePanel.message = "画像を保存します" //ウィンドウ上部に表示される
    savePanel.canCreateDirectories = true //フォルダ新規作成ボタン表示
    savePanel.showsHiddenFiles = false //隠しファイルの表示
    savePanel.showsTagField = true //タグ付のフィールド表示有無
    savePanel.tagNames = ["画像","旅行"] //デフォルト表示するタグ
    savePanel.isExtensionHidden = true //「拡張子を隠す」ボタン。Finder設定で表示になってると機能しない
    let downloadPath = NSHomeDirectory() + "/Downloads" //Download[s]なので注意
    savePanel.directoryURL = URL(fileURLWithPath: downloadPath) //デフォルト表示のフォルダ
    savePanel.canSelectHiddenExtension = false //falseだとウィンドウ中のファイルが薄く表示される。選択はできる
    savePanel.allowedFileTypes = ["jpg","gif","png"] //許可するファイルタイプ(先頭の拡張子がデフォルトファイル名に付く)
    savePanel.allowsOtherFileTypes = false //許可されたファイルタイプ以外の許可(デフォルト不許可)
    savePanel.treatsFilePackagesAsDirectoriesfalse //アプリみたいなファイルパッケージの中までディレクトリとして表示するか否か
    
    savePanel.begin { (result) in
        if result == NSApplication.ModalResponse.OK {
            guard let tiffData = saveImage?.tiffRepresentation, //画像をTIFFのDataにする
                let imageRep = NSBitmapImageRep(data: tiffData), //bitmapデータを描画?
                let imageData = imageRep.representation(using: .jpeg, properties: [.compressionFactor : NSNumber(floatLiteral: 0.7)]) //JPEGに変換
                else {
                    print("画像変換エラー")
                    return
            }
            //画像保存処理
            do {
                try imageData.write(to: savePanel.url!)
            } catch {
                print("保存エラー \(error)")
            }
        }
    }
}

コード(delegateメソッド内で保存処理する場合)

savePanelは表示だけさせて、「保存」ボタンが押されたらdelegateメソッドで処理する場合。

//画像保存
@IBAction func saveImage(_ sender: Any) {
    let saveImage = myImageView.image
    
    let savePanel = NSSavePanel()
    savePanel.title = "画像保存だってよ" //表示されない?
    savePanel.prompt = "保存" //デフォルトボタンのテキスト
    savePanel.nameFieldLabel = "名前:" //ファイル名入力欄の左に表示されるテキスト
    savePanel.nameFieldStringValue = "名称未設定" //デフォルトファイル名
    savePanel.message = "画像を保存します" //ウィンドウ上部に表示される
    savePanel.canCreateDirectories = true //フォルダ新規作成ボタン表示
    savePanel.showsHiddenFiles = false //隠しファイルの表示
    savePanel.delegate = self
    savePanel.showsTagField = true //タグ付のフィールド表示有無
    savePanel.tagNames = ["画像","旅行"] //デフォルト表示するタグ
    savePanel.isExtensionHidden = true //「拡張子を隠す」ボタン。Finder設定で表示になってると機能しない
    let downloadPath = NSHomeDirectory() + "/Downloads" //Download[s]なので注意
    savePanel.directoryURL = URL(fileURLWithPath: downloadPath) //デフォルト表示のフォルダ
    savePanel.canSelectHiddenExtension = false //falseだとウィンドウ中のファイルが薄く表示される。選択はできる
    savePanel.allowedFileTypes = ["jpg","gif","png"] //許可するファイルタイプ(先頭の拡張子がデフォルトファイル名に付く)
    savePanel.allowsOtherFileTypes = false //許可されたファイルタイプ以外の許可(デフォルト不許可)
    savePanel.treatsFilePackagesAsDirectoriesfalse //アプリみたいなファイルパッケージの中までディレクトリとして表示するか否か
    
    savePanel.runModal() //savePanelの表示
}

//savePanelで保存ボタンを押した際に呼ばれるDelegateメソッド
func panel(_ sender: Any, validate url: URL) throws {
    let saveImage = myImageView.image

    guard let data = saveImage?.tiffRepresentation,
        let imageRep = NSBitmapImageRep(data: data),
        let imageData = imageRep.representation(using: .jpeg, properties: [.compressionFactor : NSNumber(floatLiteral: 0.7)])
        else {
            print("なんかエラーっす")
            return
    }
    do {
        try imageData.write(to: url)
    } catch {
        print("保存エラー \(error)")
    }
}
プロパティはだいたいこんな感じ

問題点

savePanelのローカライズが不完全。プロパティで設定したところしか日本語化されない。
savePanelの下の方に保存するファイル形式(jpeg, gif, pngとか)を選ぶポップアップメニューを表示したいけど、やり方がわからん。