2018年5月11日金曜日

Twitter Kit SDKのサポートが終わる!?

情報ソース:ツイッター開発者ブログ

唐突だが「2018年10月31日をもって、GitHub上でiOS、 AndroidとUnityを対象にした本オープンソースに関する質問や問題を受け付けることを終了します」と公式発表された。

「Twitter Kitはみんなが簡単にツイッターコンテンツを作れるように用意したもので、去年12月にオープンソースにしてより一層盛り上げるようにしたんだけど、開発者ニーズや開発環境が大きく変わるからサポートやめたよ」という理由だという。

これって文章通りに見るなら「GitHub上での質問や問題受付のみ終了する」んだけど、まさかTwitter Kit SDKの提供や使い方の解説なんかまでやめちゃわないよな?
だってその後に、Twitter Kitを使わないでアプリにツイートを表示する方法を紹介してるんだもん。

もしアプリからツイートできなくなったりすると、かなり不便になるからなあ。

実機確認でiPhoneのつなぎっぱなしに注意

こないだようやくiPhone8買ったんだけど(今まではiPadとシミュレータで開発してた)、実機確認のためにiPhoneをUSB接続すると、USB経由のテザリングと認識されちゃう。
なぜかiPhoneの設定で「インターネット共有」をOFFにしておいてもだ。Macでテザリング相手としてiPhoneを選んでないのに。

そのため、つないだままMacでYouTubeなんて見てると、どんどんSIM容量が消費されてしまうという悲惨な結果に。
充電のためにMacにつないだまま寝たら(Macはスリープ状態)、朝になってYouTube見た時ほどじゃないけど数MB消費されてた。Macがスリープ中に何か通信したんだろうけど。

というわけで、iPhoneで実機確認のためにUSB接続する際は、容量を消費しない低速モード(そういう設定があるキャリアなら)に設定しておいた方がいいだろう。

つーか、テザリング(インターネット共有)切ってあるのになんで勝手に接続しちゃうかな…。
うちの設定が悪いんだろうか?
モバイル通信できるiPadにSIM入れて使ってた時はそんなことなかったのに。

2018年5月10日木曜日

MenuControllerを表示

この「複製/削除」ね
Text関係をタップした時なんかにコピー/カット/ペースト/ユーザ辞書…とか表示される横長のメニューあるでしょ? あれを使いたいの。

お約束としてやらなきゃならないことはあるけど、難しくないんでコードだけ書いとく。
UIViewのサブクラスとして作っておかないとcanPerformAction()とかが使えないので注意。
タップしてメニューを出したいUIをFirstResponderにするのがキモ。TextFieldとかなら、編集始めると自動でFirstResponderになってくれるからその辺の処理がいらないかも?


//Swift4
//メニュー表示
func makeMenu() -> Void {
    //先にFirstResponderにしとかないとメニューは表示されない
    becomeFirstResponder()
    //複製、削除メニュー
    let menu = UIMenuController.shared
    menu.isMenuVisible = true
    menu.arrowDirection = .up //メニューの矢印(ふきだしのしっぽみたいなの)の向き
    menu.setTargetRect(self.bounds, in: self)
    //メニューアイテム
    let menuItem1 = UIMenuItem(title: "複製", action: #selector(Fukidasi.onDuplicate(sender:)))
    let menuItem2 = UIMenuItem(title: "削除", action: #selector(Fukidasi.onDelete(sender:)))
    let menuItems = [menuItem1, menuItem2]
    menu.menuItems = menuItems
    menu.setMenuVisible(true, animated: true)

}


//ふきだしのUIViewがFirstResponderにならないとメニューは表示されない
//以前はfuncだったものが最近readOnlyでfalseを返すvarに変わったので、overrideしてtrueを返させる
override var canBecomeFirstResponder: Bool {
    return true
}

//これmenu表示に必要
override func canPerformAction(_ action: Selector, withSender sender: Any!) -> Bool {
    if action == #selector(Fukidasi.onDuplicate(sender:)) ||
        action == #selector(Fukidasi.onDelete(sender:)) {
        return true
    }
    return false
}

@objc func onDuplicate(sender: UIMenuItem) {
    print("複製された")
}

@objc func onDelete(sender: UIMenuItem) {
    print("削除された")
}

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のサイトを行ったり来たりじゃめんどくさいよね。

困ったエラー:Could not insert new outlet connection

問題

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


対処方法

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

@IBOutlet weak var test: UIBarButtonItem!

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

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

  • プロダクトのClean (⌘+Shift+K)
  • DerivedData(プロダクトのビルドフォルダーにある中間ファイル)の削除
    • 参考/[Xcode][小ネタ] DerivedDataの削除についての備忘録
    • 一部の削除(⌘+Option+Shift+K)
    • 丸ごと削除(再作成されるので消しても平気)
      • File / Project Settings... で出るダイアログの Derived Data:の下にある/Users/ユーザ名/Libr.../Xcode/DerivedData ⇨ の⇨を押して開くフォルダ中の、DerivedDataフォルダの中身の当該プロジェクトのフォルダ、もしくはDerivedDataフォルダ丸ごと消してもいい。容量でかいのでメディアの節約にもなる。
  • Xcode再起動
  • Macの再起動
  • こちらに書いてある、Xcode関係のplistをターミナルからいじる

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

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

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とか)を選ぶポップアップメニューを表示したいけど、やり方がわからん。

2018年3月31日土曜日

Xcode9.3リリース

以下、AppStoreのリリースノートを翻訳

Xcode 9.3には、iOS 11.3、watchOS 4.3、tvOS 11.3、およびmacOS High Sierra 10.13.4用のSwift 4.1およびSDKが含まれています
  • Xcode 9.3には、iOS 11.3、watchOS 4.3、tvOS 11.3、およびmacOS High Sierra 10.13.4用のSwift 4.1およびSDKが含まれています
  • [オーガナイザ]ウィンドウの[新しいエネルギー]タブには、アプリの消費電力が大きすぎるときに生成されるログが含まれます
  • 迅速なビルドタスクと他のコマンドは、ビルドのパフォーマンスを改善するために、より頻繁に並行して実行されます
  • Swiftコンパイラが-Osizeビルド設定で有効になった新しいコードサイズの最適化を追加
  • コマンドキーを押しながらシンボルをクリックすると、発信者にすばやくアクセスできます
  • アセットカタログは、ARKitアプリケーションによって現実世界で検出できるAR参照画像ファイルをサポートします
  • 新しいxccovコマンドラインツールを使用すると、コードカバレッジレポートを検査できます
  • 非常に大きなファイルで作業するときのソースエディタのパフォーマンスの向上
  • 追加のバグ修正と安定性の向上