ObjecTips

Swift & Objective-C で iOS とか macOS とか

iOS 11 UIKit の変更点

UIKit | Apple Developer Documentation

UIKit 周りざっくり、網羅はしていない。

iOS 11 関連記事
iOS 11 Foundation の変更点 - ObjecTips
iOS 11 Messages Framework の変更点 - ObjecTips
iOS 11 PDFKit - ObjecTips
iOS 11 Core Image の変更点 - ObjecTips
Vision Framework でテキスト検出 Text Detection - ObjecTips
Vision Framework で水平角検出(傾き) Horizon Detection - ObjecTips

変更点

ContentSizeCategory の比較とアクセシビリティチェックの関数が追加
UIKIT_EXTERN BOOL UIContentSizeCategoryIsAccessibilityCategory(UIContentSizeCategory category) API_AVAILABLE(ios(11.0),tvos(11.0),watchos(4.0)) NS_REFINED_FOR_SWIFT;
UIKIT_EXTERN NSComparisonResult UIContentSizeCategoryCompareToCategory(UIContentSizeCategory lhs, UIContentSizeCategory rhs) API_AVAILABLE(ios(11.0),tvos(11.0),watchos(4.0)) NS_REFINED_FOR_SWIFT;
ドラッグ&ドロップ周りの追加クラスやプロトコル

UIPasteConfiguration
UIPasteConfigurationSupporting
UIInteraction
UICollectionViewDragDelegate UICollectionViewDropDelegate
UICollectionViewReorderingCadence
UITableViewDragDelegate UITableViewDropDelegate

NSFileProviderExtension

NSFileProviderExtension - FileProvider | Apple Developer Documentation

Extension からフォルダを作ったりファイルをリネームしたり tag とか favoriteRank を編集したり出来るようになるらしい。

UITextDocumentProxy
@property (nullable, nonatomic, readonly) NSString *selectedText API_AVAILABLE(ios(11.0));

ユーザが選択中のテキストを取得できるようになった。
キーボードExtensionで入力中のテキストから再変換をかけたり絵文字に変換したりといった機能を実装出来そう。

@property (nonatomic, readonly, copy) NSUUID *documentIdentifier API_AVAILABLE(ios(11.0));

用途が思いつかない。
新しくテキスト入力しているのか、前回中断したテキスト入力作業の続きかを判断するのに使える?

UIInputViewController
@property (nonatomic, readonly) BOOL hasFullAccess API_AVAILABLE(ios(11.0));

キーボードアプリからフルアクセスが許可されているかどうかの状態チェックが可能に。
確か以前はユーザデータにアクセスしてみてアクセス出来なかったらフルアクセスが許可されていない、みたいな方法でチェックする必要があったはず。

@property (nonatomic, readonly) BOOL needsInputModeSwitchKey API_AVAILABLE(ios(11.0));

おそらくiOS 11では地球儀ボタンの表示が必要な時とそうでないケースがあるっぽい。
(追記:リリース後にiPhone XのためのAPIだと判明。iPhone Xでは地球儀マークはキーボードの左下に標準で用意される。)

UIView
@property(nonatomic) BOOL accessibilityIgnoresInvertColors API_AVAILABLE(ios(11_0), tvos(11_0));

これを YES にすると、アクセシビリティの白黒反転をViewとそのSubviewsでは反映しないように出来るらしい。
画像やビデオなど表示される色が変わると困るものに対して使うような用途を想定しているらしい。

UICollectionView
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSpringLoadItemAtIndexPath:(NSIndexPath *)indexPath withContext:(id<UISpringLoadedInteractionContext>)context API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos);

NO を返すと Spring Loading? のオプトアウトが出来るらしい。

その他 UICollectionViewDragDelegate UICollectionViewDropDelegate
UICollectionViewReorderingCadence
などドラッグ&ドロップ周りのAPIがいくつか追加

UITableView
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);

テーブルセルの横スワイプ時のボタン表示機能を提供する UITableViewRowAction の進化版の UIContextualAction が追加。
これを UISwipeActionsConfiguration と組み合わせて使うらしい。
UITableViewRowAction ではタイトルと背景色しか設定出来なかったのが画像も設定出来るようになっている。
さらに UISwipeActionsConfiguration の設定で、メールアプリで使われているようなフルスワイプした時のアクションの実行を実装出来る。(セルのスワイプで表示されるボタンを押さなくても、スワイプ操作だけでアクションを起こす事が出来るという事)

- (BOOL)tableView:(UITableView *)tableView shouldSpringLoadRowAtIndexPath:(NSIndexPath *)indexPath withContext:(id<UISpringLoadedInteractionContext>)context API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos);

NO を返すと Spring Loading? のオプトアウトが出来るらしい。

- (void)performBatchUpdates:(void (NS_NOESCAPE ^ _Nullable)(void))updates completion:(void (^ _Nullable)(BOOL finished))completion API_AVAILABLE(ios(11.0), tvos(11.0));

UICollectionView で既にあるバッチ更新が追加

その他 UITableViewDragDelegate UITableViewDropDelegate
などドラッグ&ドロップ周りのAPIがいくつか追加

UIScrollView

UIViewControllerautomaticallyAdjustsScrollViewInsets

@property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets;

が deprecated になって、代わりに UIScrollView に adjust inset 関連のAPIが色々と追加された。
今後はこっちでうまく制御しろという事か。

@property(nonatomic, readonly) UIGestureRecognizer *directionalPressGestureRecognizer API_DEPRECATED("Configuring the panGestureRecognizer for indirect scrolling automatically supports directional presses now, so this property is no longer useful.", tvos(9.0, 11.0));

ドキュメントでは追加メソッドだけどヘッダを見たら deprecated になっているのでリリースまでに消えて無くなるかも、、

UIImageView
@property (nonatomic, strong, readonly) UIView *overlayContentView UIKIT_AVAILABLE_TVOS_ONLY(11_0);

tvOS 用のメソッド。UIImageView の上に重ねてViewを表示する、、ってのがこれまで tvOS では出来なかったのかな。

UIButton
UIButtonTypePlain

tvOS 用のメソッド 。ブラー背景の無いシステム標準ボタンらしい。

UIBarButtonItem

UIBarButtonSystemItemPageCurl が deprecated

UINavigationBar
@property (nonatomic, readwrite, assign) BOOL prefersLargeTitles UI_APPEARANCE_SELECTOR API_AVAILABLE(ios(11.0)); //API_UNAVAILABLE(tvos)

iOS 11の電話アプリの様にタイトル表示を大きくする設定。

UIViewController
@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));

layoutGuide が deprecated、safeAreaLayoutGuide ってのを使うらしい。
layoutGuide は結構使ってるので分岐が面倒そう。

@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED_WITH_REPLACEMENT("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES

deprecated、UIScrollViewcontentInsetAdjustmentBehavior を使えとの事。

UISplitViewController
@property(nonatomic) UISplitViewControllerPrimaryEdge primaryEdge API_AVAILABLE(ios(11.0), tvos(11.0)); // default: UISplitViewControllerPrimaryEdgeLeading

先頭と後ろとどっちが primary かを設定出来るらしい。設定するとどう変わるのかは不明。

UINavigationItem
@property (nonatomic, readwrite, assign) UINavigationItemLargeTitleDisplayMode largeTitleDisplayMode API_AVAILABLE(ios(11.0)); //API_UNAVAILABLE(tvos)

ラージタイトルの表示モード

UINavigationBar
@property (nonatomic, readwrite, assign) BOOL prefersLargeTitles UI_APPEARANCE_SELECTOR API_AVAILABLE(ios(11.0)); //API_UNAVAILABLE(tvos)

ラージタイトルの表示設定

NSLayoutXAxisAnchor, NSLayoutYAxisAnchor
- (NSLayoutConstraint *)constraintEqualToSystemSpacingAfterAnchor:(NSLayoutXAxisAnchor *)anchor multiplier:(CGFloat)multiplier API_AVAILABLE(ios(11.0),tvos(11.0));
- (NSLayoutConstraint *)constraintGreaterThanOrEqualToSystemSpacingAfterAnchor:(NSLayoutXAxisAnchor *)anchor multiplier:(CGFloat)multiplier API_AVAILABLE(ios(11.0),tvos(11.0));
- (NSLayoutConstraint *)constraintLessThanOrEqualToSystemSpacingAfterAnchor:(NSLayoutXAxisAnchor *)anchor multiplier:(CGFloat)multiplier API_AVAILABLE(ios(11.0),tvos(11.0));

- (NSLayoutConstraint *)constraintEqualToSystemSpacingBelowAnchor:(NSLayoutYAxisAnchor *)anchor multiplier:(CGFloat)multiplier API_AVAILABLE(ios(11.0),tvos(11.0));
- (NSLayoutConstraint *)constraintGreaterThanOrEqualToSystemSpacingBelowAnchor:(NSLayoutYAxisAnchor *)anchor multiplier:(CGFloat)multiplier API_AVAILABLE(ios(11.0),tvos(11.0));
- (NSLayoutConstraint *)constraintLessThanOrEqualToSystemSpacingBelowAnchor:(NSLayoutYAxisAnchor *)anchor multiplier:(CGFloat)multiplier API_AVAILABLE(ios(11.0),tvos(11.0));

iOS 11では system space っていう概念が追加されるらしい。

UIImagePickerController
@property(nonatomic)           UIImagePickerControllerQualityType    videoQuality NS_DEPRECATED_IOS(3_1, 11_0, "Use videoExportPreset");         // default value is UIImagePickerControllerQualityTypeMedium. If the cameraDevice does not support the videoQuality, it will use the default value.

取得するビデオの画質設定が deprecated、videoExportPreset を使えとの事。

@property(nonatomic, copy)     NSString                              *videoExportPreset NS_AVAILABLE_IOS(11_0);   // default value is AVAssetExportPresetMediumQuality. NSString (of one of the AVAssetExportPreset* constants). // Specifying a value for videoExportPreset will override a value specified for videoQuality

取得するビデオの画質設定方法が AVFoundation ベースに変更。プリセットは豊富なので以前より柔軟になった。

@property(nonatomic) UIImagePickerControllerImageURLExportPreset imageExportPreset NS_AVAILABLE_IOS(11_0);   // default value is UIImagePickerControllerImageExportPresetCompatible.

取得する画像の画質設定っぽいけど、ドキュメントにもヘッダにも詳細の記載が無いので不明

UIKIT_EXTERN NSString *const UIImagePickerControllerReferenceURL        NS_DEPRECATED_IOS(4_1, 11_0, "Replace with public API: UIImagePickerControllerPHAsset") __TVOS_PROHIBITED;  // an NSURL that references an asset in the AssetsLibrary framework
UIKIT_EXTERN NSString *const UIImagePickerControllerPHAsset NS_AVAILABLE_IOS(11_0) __TVOS_PROHIBITED;  // a PHAsset
UIKIT_EXTERN NSString *const UIImagePickerControllerImageURL NS_AVAILABLE_IOS(11_0) __TVOS_PROHIBITED;  // an NSURL

URL取得のキーが deprecated になって、PHAsset と画像のURLを指定して取得出来る様になった。

UIDocumentBrowserViewController

ローカルとiCloudのファイルにアクセス出来るらしい。
表示する contentType の指定やドキュメント作成の可否、複数項目の選択可否など macOS の NSOpenPanel ライクに使えそう。

UIDocumentPickerViewController
@property (nonatomic, assign) BOOL allowsMultipleSelection NS_AVAILABLE_IOS(11_0);
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls NS_AVAILABLE_IOS(11_0);

複数項目の選択取得に対応

UIActivityType
UIActivityTypeMarkupAsPDF

アクティビティタイプの追加

Drag & Drop

追加が多いので以下URLで

Drag and Drop | Apple Developer Documentation

Drag and Drop Customization | Apple Developer Documentation

Accessibility
UIKIT_EXTERN NSString *const UIAccessibilityVoiceOverStatusChanged API_DEPRECATED_WITH_REPLACEMENT("UIAccessibilityVoiceOverStatusDidChangeNotification", ios(4.0, 11.0), tvos(9.0, 11.0));
UIKIT_EXTERN NSNotificationName const UIAccessibilityVoiceOverStatusDidChangeNotification API_AVAILABLE(ios(11.0), tvos(11.0));

ボイスオーバーのオンオフの変更通知の名称が変更。StatusChangedStatusDidChangeNotification のワードに変更しただけ。

UIGestureRecognizer
@property (nullable, nonatomic, copy) NSString *name API_AVAILABLE(ios(11.0), tvos(11.0)); // name for debugging to appear in logging

identifier として name を設定出来る様になった。ただヘッダのコメントではデバッグのためって書いてあるので、name で処理分岐を行ったりするのは適切では無さそう。

UIGraphicsImageRendererFormat
+ (instancetype)formatForTraitCollection:(UITraitCollection *)traitCollection NS_AVAILABLE_IOS(11_0);

UITraitCollection を引数に format を作成してくれるメソッドらしいだけど、scale とか UIGraphicsImageRendererFormat のパラメータを自分で設定するなら使う機会が無いかも。

UIColor
+ (nullable UIColor *)colorNamed:(NSString *)name NS_AVAILABLE_IOS(11_0);      // load from main bundle
+ (nullable UIColor *)colorNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_AVAILABLE_IOS(11_0);

Named Color Assets 関連のメソッド。
Xcode 9では xcassets で画像と同じ様に色をアセットとして作成する事ができて、これらのメソッドで呼び出して使ったり Storyboard 上からも呼び出す事ができる。
カラーアセットを使えば全ボタンの色を変更したりアプリ全体の色管理が容易になるはず。

UIFontMetrics

ドキュメント無し

UITextInputTraits
UIKIT_EXTERN UITextContentType const UITextContentTypeUsername                  NS_AVAILABLE_IOS(11_0);
UIKIT_EXTERN UITextContentType const UITextContentTypePassword                  NS_AVAILABLE_IOS(11_0);

キーボードの入力タイプにユーザ名とパスワード用の入力タイプが追加

UITextSmartQuotesType
UITextSmartDashesType
UITextSmartInsertDeleteType

macOS のスマート引用符とスマートダッシュに相当する入力テキストの自動置き換え機能だと推測。
最後のは英語キーの時のスペースの自動入力と削除?


以下UIKitじゃ無いけどUI周りなのでまとめて

EventKitUI
EVENTKITUI_EXTERN NSBundle *EventKitUIBundle(void);

これ Private で使ってるAPIじゃないの?開発者には利用のしようが無い気がする。

MessageUI MFMailComposeViewController
- (void)setPreferredSendingEmailAddress:(NSString *)emailAddress API_AVAILABLE(ios(11.0));

メール送信UIで送信元(自分)のアドレスを指定出来る様になったらしい。
アプリの使用者のメールアドレスを把握している必要があるので、アカウント制のアプリでアカウントに紐付いたメールアドレスが分かっている場合や、利用ユーザを管理・把握している社内向けの業務アプリとかで活用出来そう。


まとめ

Named Color Assets の機能が最高。これは本当に色管理が楽になるはず。
あと UIDocumentBrowserViewController はユーザにとって便利で、これに対応しているどうかでアプリを選ぶ人も出てきそうな気がするので積極的に使っていきたい。
開発的にもこのクラスを使っておけばドキュメントの受け渡しの面倒な処理をしなくて良さそうなのでいい事尽くめだと思う。

App Storeでレビューする機能を実装する

iOS 10.3から登場する SKStoreReviewController のドキュメントにApp Storeのレビュー画面を直接開く方法がしれっと記載されている。

https://developer.apple.com/reference/storekit/skstorereviewcontroller/2851536-requestreview

アプリのURLのクエリに action=write-review を追加すればいいとの事。
例えば Apple の TestFight アプリを例にすると以下のコードになる

- (IBAction)review:(id)sender
{
    // 899247664 is identifier for TestFlight
    NSURL *URL = [NSURL URLWithString:@"https://itunes.apple.com/app/id899247664?action=write-review"];
    [[UIApplication sharedApplication] openURL:URL options:@{} completionHandler:nil];
}

この時の遷移の流れは以下のようになる

メソッド実行

f:id:Koze:20170202101009p:plain

アプリを離れて App Store へ遷移

f:id:Koze:20170202101258p:plain

レビュータブが選択された状態のアプリ紹介画面が一瞬表示される

f:id:Koze:20170202101404p:plain

即座にモーダルでレビュー記入画面が表示される(ログインが必要であればログイン)

f:id:Koze:20170202101636p:plain f:id:Koze:20170202101703p:plain

キャンセルorレビューを完了するとアプリ紹介画面へ戻る

f:id:Koze:20170202101404p:plain

このように action=write-review を使うと直接レビュー画面を開く事が出来る。
クエリ無しでURLを開いた場合はアプリ紹介画面の詳細タブが選択された状態で表示されるためユーザにレビュータブを選択してもらう操作が必要になる。
その点はスマートではないけどレビュータブを選択してレビューを書く前に「App サポート」の選択肢がユーザに提示されるので、もしアプリの使い方や不具合についてネガティブなレビュー書く前にサポートに連絡して欲しいという場合はクエリを使って直接レビュー記入画面を開かずに従来の方法でApp Storeに遷移するのも手かも知れない。
もしくはアプリ内でレビューボタンと不具合報告ボタンをきちんと分けておいて、不具合報告はサポートに直接連絡してもらうようなフローを取るのがいいと思う。


ちなみに iOS 10.3 の SKStoreReviewController を使ったレビュー機能はアプリ内にアラート形式のレーティング画面が出るだけなので非常に使いやすそうだけど、ドキュメントにあるようにメソッドを実行した時に常にアラートが出るわけではなくframework側でよしなに実行タイミングを決めてくれるものなので、ボタン操作などユーザのアクションに対してこのコントローラを使うのは不適切らしい。

UIImageView の画像をフェードインアウトさせて変更する

UIImageView に画像を設定する際にフワッとフェード(ディゾルブ)するようにするには、CALayerCATransition のアニメーションを加えてやる

コードは以下

    UIImageView *imageView = self.imageView;
    imageView.image = image; // 画像を変更
    [imageView.layer addAnimation:[CATransition animation] forKey:nil]; // アニメーションを追加

これでOK

CATiledLayer のフェードインエフェクトを解除する

CATiledLayer は表示時にデフォルトでフェードインエフェクトがかかるが CATiledLayer のサブクラスを作って以下のクラスメソッドをオーバーライドして 0 秒を返してやる事でフェードインしないようにする事が出来る。

+ (CFTimeInterval)fadeDuration;

実装は以下

あとは通常通り自前の描画処理を書くだけ。

カメラで撮影した写真と動画を Photos.framework でライブラリに保存する

撮影は UIImagePickerController にお任せして撮影後のデータを Photos で保存する実装方法。

まず UIImagePickerControllersourceType でカメラ撮影を指定。
デフォルトのままだと写真撮影しかできないので、カメラ撮影で利用可能な形式を全て設定して動画撮影もできる様に mediaTypes を設定する。
これで写真と動画の撮影はOK

    UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera;
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.sourceType = sourceType;
    picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType];
    picker.delegate = self;
    [self presentViewController:picker animated:YES completion:nil];

撮影したデータの取得は UIImagePickerControllerDelegate のデリゲートメソッドで行う

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info

写真撮影時は info に UIImagePickerControllerOriginalImage キーで UIImage で画像データが入ってきて、動画撮影時には UIImagePickerControllerMediaURLNSURL で動画ファイルのURLが入ってくるという違いがあるので撮影方法によって条件分岐して処理する必要がある。
どちらで撮影されたかは info から UIImagePickerControllerMediaType キーで取得して判定できる。

    NSString *mediaType = info[UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
    }
    else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {
    }

なおここの kUTTypeImagekUTTypeMovie のキーを使うために MobileCoreServices.framework も import しておく必要がある。
分岐の後、画像であれば PHAssetChangeRequest

+ (instancetype)creationRequestForAssetFromImage:(UIImage *)image;

動画ファイルのURLであれば

+ (nullable instancetype)creationRequestForAssetFromVideoAtFileURL:(NSURL *)fileURL;

これらのメソッドを使ってライブラリへのデータ保存を行う。

PHAssetChangeRequest は若干変わった利用方法になっていて、以下のように PHPhotoLibrary の変更を行うブロック内でこのインスタンスを作成すると PHPhotoLibrary が勝手に保存処理を行ってくれる。
目に見えて解りやすい save, write などの直接的なメソッド呼び出しでの保存ではないのでちょっと注意。

    PHPhotoLibrary *library = [PHPhotoLibrary sharedPhotoLibrary];
    [library performChanges:^{
            [PHAssetChangeRequest creationRequestForAssetFromImage:image];
    } completionHandler:^(BOOL success, NSError * _Nullable error) {
    }];

コード全体は以下のようになる

Xcode プロジェクトの watchOS 1 から watchOS 2 への移行

watchOS 1 から watchOS 2 への移行は、Xcode のプロジェクトを選択して
Editor > Validate Settings...
を選択して Xcode にお任せてプロジェクト更新するのが良い。
でもなぜかこの時に watchOS の移行についての項目が出てこなくてプロジェクトがうまくアップデートされない事があった。
その際 Xcode プロジェクト hoge.xcodeproj のパッケージを開いて中の project.pbxproj ファイルを直接エディタで開き、
以下のように記載されているところを

productType = "com.apple.product-type.application.watchapp";
productType = "com.apple.product-type.watchkit-extension";

それぞれ以下のように変更したら Xcode がちゃんと watchOS 2 のターゲットだと認識してうまくビルドできるようになった。
productType = "com.apple.product-type.application.watchapp2";
productType = "com.apple.product-type.watchkit2-extension";


あまり無いケースだと思うけど、もし同様の問題が起きたら上記の様な事はしないで一旦 git でプロジェクトを元の状態に戻してキャッシュを消すなり Xcode を起動し直すなりして正しく Validate Settings が完了する様リトライした方が良い(と後から思った)。
参考程度に。

自作の iOS Framework を watchOS に対応させる

自作Farmework内で watchOS で利用できないAPIを使っていると not available on watchOS のエラーが出て watchOS 向けにビルドできない。
Apple純正の Framework と同じようにヘッダのメソッド定義に __WATCHOS_PROHIBITED を付けて watchOS では使えません宣言をすると ifdef を使ったりせずメソッドをそのままビルド可能になる。

ビルドエラー
// .h
- (void)test:(EKEventStore *)eventStore;

// .m
- (void)test:(EKEventStore *)eventStore
{
    [eventStore commit:nil]; // 'commit:' is unavailable: not available on watchOS
}
OK
// .h
- (void)test:(EKEventStore *)eventStore __WATCHOS_PROHIBITED;

// .m
- (void)test:(EKEventStore *)eventStore
{
    [eventStore commit:nil];
}

クラス自体を watchOS で使えないように宣言する場合はクラス宣言の @interface の前にこれを付けると良い

__WATCHOS_PROHIBITED @interface MyClass : NSObject