macOS で iOS/Android(ADB) デバイスの接続・切断を検出したくて、この数日ずっと調べていてようやく形にできた。
備忘録的にどうやっているのかを簡単に書いておこうと思う。
続きを読むXamarin Advent Calendar 2016 17日目です。
以前から僕はネイティブバインディング関連の記事をいろいろ書いてきました。
今回もネイティブバインディングです!
Swift が使われるようになってしばらくたち、CocoaControlsに新しく出てきている今風の UI ライブラリはほとんど Swift 製になりつつあります。 Swift を使う場合、Swift のサポートライブラリ(ランタイム?)が必要となり、Swift で作ったアプリはデバッグ実行やアドホック配布はできるけどストアに上がらない…!というのはネイティブでもよくあります。
Xamarin でのネイティブバインディングも Objective-C と異なる手順が必要となります。どのようにすればいいかをご紹介します。
今回は Objective Sharpie で TTSegmentedControl (CocoaControls, GitHub) のバインディングをやってみましょう。
使用したいライブラリが決まったら、そのライブラリが対象としている Swift のバージョンを確認しましょう。これにより、この後使用する SwiftSupport の NuGet も変わってきます。
また、異なるバージョンの Swift で書かれたライブラリの混在はできない(ネイティブでもできない)ので、注意しましょう。
Swift バージョン | Xcode バージョン | iOS SDK のバージョン | SwiftSupport の NuGet |
---|---|---|---|
Swift 2.2 | Xcode 7.3.1 | iphoneos9.3 | Xamarin.SwiftSupport |
Swift 2.3 | Xcode 8.x | iphoneos10.x | Xamarin.Swift23.Support |
Swift 3.0 | Xcode 8.x | iphoneos10.x | (現段階で存在せず) |
もし、対応する Xcode を持っていない場合、Downloads for Apple Developers というページからダウンロードしましょう。 このページには過去の Xcode がアーカイブされています。
複数の Xcode を環境中に共存することもできます。Xcodeの複数バージョンを共存をご覧ください。
TTSegmentedControl は GitHub の README.md にもあるとおり、Swift 3.0 と 2.x のどちらでも使えますが、2.x を使う場合は 0.1.1 を指定する必要があります。 今回はこれを Swift 2.3 で使うこととし、Xcode 8.2 を使って作業していきます。
使った Objective Sharpie のバージョンは以下の通りです。
$ sharpie -v 3.3.0p 3d8547f 3d8547fcf1f7c2c2337b122c8d1fe893eb6a1ad3 2016-08-03 04:49:53
まず、sharpie pod init でソースコードをダウンロードします。
$ sharpie pod init iphoneos10.2 TTSegmentedControl
プロジェクトが作成され、ライブラリのソースコードがダウンロードされますが、バージョン指定して Swift 2.x で使えるバージョンをダウンロードし直します。 できた Podfile を編集して、0.1.1 指定にします。
platform :ios, '10.2' install! 'cocoapods', :integrate_targets => false target 'ObjectiveSharpieIntegration' do use_frameworks! end pod 'TTSegmentedControl', '0.1.1'
Podfile を編集したら、pod install
で反映します。
$ pod install
次に、できた Pods/Pods.xcodeproj を確認します。
目的とするライブラリのターゲットの Enable Bitcode を No にします。(これをしておかないと、ストア申請時に Invalid Bitcode エラーとなります)
Xcode 8.x で Swift 2.3 を使うときは Swift Use Legacy Swift Version を明示指定する必要があります。
Deployment Target も必要とするバージョンまで下げておきましょう。
あとは、sharpie pod bind でビルドします。
$ sharpie pod bind
Mac インストール直後で requires Xcode といわれてしまう場合は、xcode-select コマンドを実行してコマンドラインで使用する Xcode を指定してやりましょう。
$ sudo xcode-select --switch /Applications/Xcode.app
できた Binding フォルダの中にある .framework と .cs が必要になります。
が、これだけではシミュレーターで実行できない状態となります。 Objective Sharpie 実行時点では実機用のバイナリしか生成されないため、シミュレーター向けのビルドは別途手動で実行し、実機向けのビルドと合成させる必要があります。
まずは、シミュレーター向けの .framework を作ります。Pods フォルダに移動して、xcodebuild コマンドでビルドしましょう。 最後の codesign の結果で、どのパスに生成されたかがわかります。
$ cd Pods $ xcodebuild -project Pods.xcodeproj -target TTSegmentedControl -sdk iphonesimulator10.2 -configuration Release build (中略) Signing Identity: "-" /usr/bin/codesign --force --sign - --timestamp=none /Users/***/Projects/TTSegmentedControlSample/Native/build/Release-iphonesimulator/TTSegmentedControl/TTSegmentedControl.framework
できたシミュレータ用の Framework のバイナリと、Objective Sharpie が生成したネイティブのライブラリを合成します。 lipo コマンドを使い合成した結果で Objective Sharpie の出力を置き換えます。
lipo -create [合成するバイナリ] [合成するバイナリ] -output [出力先のバイナリ]
lipo -info [確認するバイナリ]
$ cd .. $ ls Binding Podfile Podfile.lock Pods build $ lipo -create Binding/TTSegmentedControl.framework/TTSegmentedControl /Users/***/Projects/TTSegmentedControlSample/Native/build/Release-iphonesimulator/TTSegmentedControl/TTSegmentedControl.framework/TTSegmentedControl -output TTSegmentedControl $ ls Binding Podfile Podfile.lock Pods TTSegmentedControl build $ lipo -info TTSegmentedControl Architectures in the fat file: TTSegmentedControl are: i386 x86_64 armv7 arm64 $ mv TTSegmentedControl Binding/TTSegmentedControl.framework/TTSegmentedControl
また、i386 と x86_64 の SwiftModule も必要となるので、あわせてコピーします。
$ cp /Users/***/Projects/TTSegmentedControlSample/Native/build/Release-iphonesimulator/TTSegmentedControl/TTSegmentedControl.framework/Modules/TTSegmentedControl.swiftmodule/* Binding/TTSegmentedControl.framework/Modules/TTSegmentedControl.swiftmodule/ $ ls Binding/TTSegmentedControl.framework/Modules/TTSegmentedControl.swiftmodule/ arm.swiftdoc arm.swiftmodule arm64.swiftdoc arm64.swiftmodule i386.swiftdoc i386.swiftmodule x86_64.swiftdoc x86_64.swiftmodule
あとは、新しくなったObjective SharpieでCocoaPodsのバインディングライブラリを作る とほとんど手順は同じなのですが、1つだけ異なる点があります。
.framework のライブラリは「ネイティブ参照」のところに追加する必要があります。右クリックして、Add Native Reference を選択します。
.framework を選択すると、中身が見えてしまいますが、.framework がアクティブになった状態のまま Open をクリックして追加します。
プロパティを開いて Force Load のチェックを入れ、使用する Framework を指定しておきます。(Dark スキームだとちょっと見づらいです)
次は ApiDefinition の作成です。まず、Swift のクラスはネイティブライブラリ内でも名前が変わってしまうので、別名指定を BaseTypeAttribute に追加する必要があります。 .framework の中にできた Headers/*-Swift.h の中にクラスの名前が SWIFT_CLASS マクロで書かれています。
SWIFT_CLASS("_TtC18TTSegmentedControl18TTSegmentedControl") @interface TTSegmentedControl : UIView
これを、BaseType の Name に追加してやります。
[BaseType(typeof(UIView), Name = "_TtC18TTSegmentedControl18TTSegmentedControl")] interface TTSegmentedControl
また、Swift 側でプライベートのエクステンションを多く使っている場合は空のインターフェイスが大量に生成されるのと、UIKit の override など、BaseType が持っているようなメソッドの定義は削除しておきましょう。
今回の ApiDefinition.cs は以下のようになりました。
using System; using UIKit; using Foundation; using ObjCRuntime; using CoreGraphics; namespace TTSegmentedControl { // @interface TTSegmentedControl : UIView [BaseType(typeof(UIView), Name = "_TtC18TTSegmentedControl18TTSegmentedControl")] interface TTSegmentedControl { // @property (nonatomic, strong) UIFont * _Nonnull defaultTextFont; [Export("defaultTextFont", ArgumentSemantic.Strong)] UIFont DefaultTextFont { get; set; } // @property (nonatomic, strong) UIFont * _Nonnull selectedTextFont; [Export("selectedTextFont", ArgumentSemantic.Strong)] UIFont SelectedTextFont { get; set; } // @property (nonatomic, strong) UIColor * _Nonnull defaultTextColor; [Export("defaultTextColor", ArgumentSemantic.Strong)] UIColor DefaultTextColor { get; set; } // @property (nonatomic, strong) UIColor * _Nonnull selectedTextColor; [Export("selectedTextColor", ArgumentSemantic.Strong)] UIColor SelectedTextColor { get; set; } // @property (nonatomic) BOOL useGradient; [Export("useGradient")] bool UseGradient { get; set; } // @property (nonatomic, strong) UIColor * _Nonnull containerBackgroundColor; [Export("containerBackgroundColor", ArgumentSemantic.Strong)] UIColor ContainerBackgroundColor { get; set; } // @property (nonatomic, strong) UIColor * _Nonnull thumbColor; [Export("thumbColor", ArgumentSemantic.Strong)] UIColor ThumbColor { get; set; } // @property (copy, nonatomic) NSArray<UIColor *> * _Nullable thumbGradientColors; [NullAllowed, Export("thumbGradientColors", ArgumentSemantic.Copy)] UIColor[] ThumbGradientColors { get; set; } // @property (nonatomic, strong) UIColor * _Nonnull thumbShadowColor; [Export("thumbShadowColor", ArgumentSemantic.Strong)] UIColor ThumbShadowColor { get; set; } // @property (nonatomic) BOOL useShadow; [Export("useShadow")] bool UseShadow { get; set; } // @property (nonatomic) CGSize padding; [Export("padding", ArgumentSemantic.Assign)] CGSize Padding { get; set; } // @property (nonatomic) CGFloat cornerRadius; [Export("cornerRadius")] nfloat CornerRadius { get; set; } // @property (copy, nonatomic) NSArray<NSString *> * _Nonnull itemTitles; [Export("itemTitles", ArgumentSemantic.Copy)] string[] ItemTitles { get; set; } // @property (copy, nonatomic) void (^ _Nullable)(NSInteger, NSString * _Nullable) didSelectItemWith; [NullAllowed, Export("didSelectItemWith", ArgumentSemantic.Copy)] Action<nint, NSString> DidSelectItemWith { get; set; } // @property (nonatomic) BOOL allowDrag; [Export("allowDrag")] bool AllowDrag { get; set; } // @property (nonatomic) BOOL allowChangeThumbWidth; [Export("allowChangeThumbWidth")] bool AllowChangeThumbWidth { get; set; } } // @interface TTSegmentedControl_Swift_157 (TTSegmentedControl) [Category] [BaseType(typeof(TTSegmentedControl))] interface TTSegmentedControl_TTSegmentedControl_Swift_157 { // -(void)selectItemAtIndex:(NSInteger)index animated:(BOOL)animated; [Export("selectItemAtIndex:animated:")] void SelectItemAtIndex(nint index, bool animated); } }
今回は同一ソリューションに iOS の Single View アプリケーションを追加したので、プロジェクト参照でバインディングライブラリを参照しました。
また、Swift を使ったライブラリを使う場合、SwiftSupport の NuGet ライブラリが必要となりますので、パッケージを追加しておきます。
さて、このまま AdHoc ビルドをすると署名が合わずインストールができなくなる場合があります。これは、SwiftSupport のパッケージがビルドの終盤でシミュレータ用のバイナリも一度まとめてコピーして、シミュレータじゃなかった場合は消すという動きをするので、署名が微妙に合わなくなるのです。
このようなときはシミュレータ向けのバイナリを消すタイミングを変更することで回避できます。.csproj を開き、以下の Target を追加してください。
<Target Name="RemoveSimulatorSymbols" AfterTargets="_CopyResourcesToBundle" Condition="'$(Platform)' == 'iPhone'"> <Exec Command="rm -rf $(OutputPath)/$(AssemblyName).app/SwiftFrameworksSimulator" /> </Target>
さて、いよいよストアに申請という段になっても、また別の作業が必要です。Swift を使うアプリの場合、ipa の中に SwiftSupport というものを持っておく必要があります。 Xcode の中に入っている Swift のライブラリを追加するという作業になるのですが、この作業をするためのスクリプト auto-ipa-packager が公開されています。
Xamarin Studio から出力されたストアビルドに対して、このスクリプトを実行することで、ストア申請用の SwiftSupport を追加することができます。
このコマンドを使うとき、使用している Swift ライブラリにあった Xcode の中にある Developer ディレクトリを DEVELOPER_DIR 環境変数として指定する必要があります。(Xcode とライブラリの Swift バージョンが一致しない場合は Invalid Swift Support となってしまいます)
$ DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer sh auto_package_ipa.sh Hoge.ipa
また、この作業でもパッケージの署名がおかしくなる場合があります。このような場合は、fastlane の sigh コマンドを使って再署名してやります。
$ sigh resign ./Hoge.ipa --signing_identity 'iPhone Distribution: Nobuhiro Ito (XXXXXXXXXX)' -p net.iseteki.hoge=EbiSoft_Hoge_for_AppStore.mobileprovison
Swift のライブラリを使うのはまだまだ大変ですが、今のおしゃれな UI コンポーネントは Swift が多いので、ぜひ覚えておいて損はないかと思います。
ただ、こんな手のかかることやりたくないというのも事実ですので、Xamarin 自体の Swift サポートが強化されるのを願いたいと思います。
先日、MacBook Pro が起動しなくなりました。救出ボリュームから起動したらSSDが認識されていません。そうMacBook Pro 13-inch (Early 2011) に Fusion Drive を搭載したら BootCamp 作れなくなった話 - backyard of 伊勢的新常識でも書いた通り、Fusion Drive にしていたので、SSDとHDDのどちらか片方が死んだら起動できなくなるのです。
ですので、TimeMachineを設定していたのですが、よくないことは続くもので、数日前から TimeMachine をマウントできておらず、この週末に直そうと思っていたところなのです。
最近は多くのデータをDropboxやOneDriveといったクラウド上に置いていたので、作業中だった夏コミの原稿を含めて無事だったのですが、問題は手元にしかないiTunesライブラリです。かれこれ15年近く育ててきたライブラリが一瞬で終了したわけです。
このままあきらめるわけにはいかない。SSDはもうマウントすらできなくなったFusion Driveはもう戻ってこないので諦めて、HDDだけの構成にしてMacBookを仮復旧。TimeMachine を救出することを考えて、なんとかデータの吸出しがはじまりました。
まだコピー中ですが、とりあえず手順のメモをかねて。
伊勢的新常識はコミックマーケット90に参加します。スペースは3日目 西f-04bです。
また今回も、id:tmyt とスマートフォンアプリを黒い方面になんやかんやする内容のコピー本を出します。今のところ、こういうネタ予定してます。やる気があれば小ネタもう1つくらい足すかも。ページ数決まってないのでなんともですが、500円以下にはなりそうです。
もう夏しか参加できないのに、夏2連敗してたので、すごい久しぶりです。活動の空きが年単位になりますね。
3日目 西f-04b でお待ちしておりますので、何かのついでにお立ち寄りいただければ幸いです。(今回はちゃんと本人います!)
先日 Project Rider EAP で Xamarin を書けるか とかいう記事を書いたわけですが、ついに公式に Xamarin がサポートされました。
公式にはどちらかというと、.NET Core RTM やデバッグサポートのほうが先に来てたのですが、かなり待ってただけに当初 Xamarin Support の字しか目に入りませんでした!(ぁ)
とりあえず軽く触ってみた感想を書いてみます。
というわけで、コード書く分には ReSharper 相当の支援機能や Android Studio 同様のアドインが使えるのでかなり快適です。Xamarin Studio と Rider の両方で開きながら、リソース編集や実行を Xamarin Studio でやれば今のところはいけるのだろうか。しばらく使ってみようと思います。
Xamarin の Cycle 7 アップデートが Stable になり、Xamarin Studio 6.0 が Stable に降ってきました。
Cycle 7 全体については、Stable Release: Cycle 7 | Xamarin Releases からリンクをたどれますが、Xamarin Studio 6.0 のリリースノート の Text Editor のところを見たら、Alpha のときから消えなかった "This release does not include the VI mode." という一文があります。Stable になるときに消えるかなーと思ってたのですが、ついになくなってしまったようです。
ぎゃー!やっぱりあらへんやんー!Stableに来るころには帰ってきてるとおもったのに… pic.twitter.com/v7GHoxH3u6
— いせ (@iseebi) 2016年6月8日
Vimmer は多少実装が残念だったとしても、hjkl でカーソルが動かないとコード書くことができません。これでは仕事にならない…と途方に暮れてしまっていたのですが、id:atsushieno さんにコミュニティアドインがあることを教えていただきました。
続きを読む@iseebi なくなりましたね https://t.co/5AnJZa2gpL まあもともと不評だったみたいだし、コミュニティアドインあるみたいですよ
— Atsushi Eno (@atsushieno) 2016年6月8日