iOS-OpenCV(2):写真のキャプチャ、保存、共有



Ios Opencv Capture



まず、VideoCameraの定義

OpenCVはクラスを提供します CvVideoCamera 高度なカメラ制御とプレビューGUIを実装しますが、高度にカスタマイズされたものをサポートします。 CvVideoCamera 設立 AVFoundation 上記、およびいくつかの基礎となるクラスへのアクセスを提供します。したがって、アプリケーション開発者は高度な使用を選択できます CvVideoCamera 機能的で低レベル AVFoundation 機能の組み合わせ。アプリケーション開発者はほとんどのGUIを実装し、ビデオプレビューを無効にするか、指定することができます CvVideoCamera その親ビューがレンダリングされます。さらに、アプリケーションはキャプチャされた各ビデオフレームを処理でき、アプリケーションがキャプチャされたフレームをインプレースで編集する場合は、 CvVideoCamera 結果はプレビューに表示されます。したがって、 CvVideoCamera これは、エンジニアリングの出発点として適しています。

  • OpenCVは、連続ビデオストリームの代わりに高品質の静止画像をキャプチャするためのCvPhotoCameraと呼ばれるクラスも提供します。 CvVideoCameraとは異なり、CvPhotoCameraではカスタム画像処理をライブプレビューに適用することはできません。
  1. カスタマイズ CvVideoCamera
    と呼ばれるものを作成します CvVideoCamera 名前付きサブクラス VideoCamera.h 新しいヘッダーファイル。ここでは、次のコードに示すように、新しいプロパティとメソッドを含む、サブクラスのパブリックインターフェイスを宣言します。
#import @interface VideoCamera : CvVideoCamera @property (nonatomic,assign) BOOL letterboxPreview - (void)setPointOfInterestInParentViewSpace:(CGPoint)point @end

-(void)setPointOfInterestInParentViewSpace:(CGPoint)point このメソッドは、カメラのオートフォーカスおよび自動露出アルゴリズムのフォーカスを設定します。単に最適なソリューションを検索した後、カメラは、焦点距離と中間調レベルが、プレビューの親ビューのピクセル座標で表される特定のポイントの近傍と一致するように、それ自体を再構成する必要があります。つまり、調整後、ポイントとその周辺に焦点を合わせ、約50%グレーにする必要があります。ただし、優れた自動露出アルゴリズムでは、シーンの色やその他の領域に基づいて明るさを変化させることができます。
クラスの実装ファイル VideoCamera.m コードでは、プロパティを持つプライベートインターフェイスを追加します。 customPreviewLayer 次のコードに示すように、レイヤー:



#import 'VideoCamera.h' @interface VideoCamera () @property (nonatomic, strong) CALayer *customPreviewLayer @end

プレビューレイヤーのレイアウトをカスタマイズするために、書き直します CvVideoCamera 次の方法:

  • (int)imageWidthおよび(int)imageHeight: これらのメソッドは、カメラで現在使用されている水平解像度と垂直解像度を返す必要があります。スーパークラスの実装は、現在の解像度を直接照会するのではなく、さまざまな品質モードでのデフォルトの解像度に関する一連の仮定に依存しているため、(OpenCV 3.1では)間違っています。
  • (void)updateSize: スーパークラスは、このメソッドを使用してカメラの解像度を想定します。これは実際には逆効果のアプローチです。前の箇条書きで説明したように、この仮定は信頼性が低く、不要です。
  • (void)layoutPreviewLayer: このメソッドは、現在のデバイスの向きを尊重する方法でプレビューを配置する必要があります。スーパークラスの実装が間違っています(OpenCV 3.1)。場合によっては、プレビューが引き伸ばされたり、方向が正しくなかったりします。

正しい解像度を得るために、名前を渡すことができます AVCaptureVideoDataOutputAVFoundation このクラスは、カメラの現在のキャプチャパラメータを照会します。以下のコードを参照してください、コードは書き直されます imageWidth ゲッターメソッド:



- (int)imageWidth { AVCaptureVideoDataOutput *output = [self.captureSession.outputs lastObject] NSDictionary *videoSettings = [output videoSettings] int videoWidth = [[videoSettings objectForKey:@'Width'] intValue] return videoWidth }

同様に、書き直してみましょう imageHeight ゲッターメソッド:

- (int)imageHeight { AVCaptureVideoDataOutput *output = [self.captureSession.outputs lastObject] NSDictionary *videoSettings = [output videoSettings] int videoHeight = [[videoSettings objectForKey:@'Height'] intValue] return videoHeight }

これで、カメラの解像度を照会する問題が完全に解決されました。したがって、空の実装で書き直すことができます updateSize 方法:

- (void)updateSize { // Do nothing. }

ビデオプレビューが表示されたら、最初に親ビューに配置します。次に、アスペクト比のアスペクト比とプレビュープレビューのサイズを見つけます。万一に備えて letterboxpreviewはい プレビュー表示は、親ビューのサイズよりも小さい場合があります。そうしないと、親ビューの1つよりも大きくなる可能性があり、その場合、そのエッジがビューの外側に表示される可能性があります。以下のコードは、プレビューを配置および縮小する方法を示しています。



- (void)layoutPreviewLayer { if (self.parentView != nil) { // Center the video preview. self.customPreviewLayer.position = CGPointMake(0.5 * self.parentView.frame.size.width, 0.5 * self.parentView.frame.size.height) // Find the video's aspect ratio. CGFloat videoAspectRatio = self.imageWidth / (CGFloat)self.imageHeight // Scale the video preview while maintaining its aspect ratio. CGFloat boundsW CGFloat boundsH if (self.imageHeight > self.imageWidth) { if (self.letterboxPreview) { boundsH = self.parentView.frame.size.height boundsW = boundsH * videoAspectRatio } else { boundsW = self.parentView.frame.size.width boundsH = boundsW / videoAspectRatio } } else { if (self.letterboxPreview) { boundsW = self.parentView.frame.size.width boundsH = boundsW / videoAspectRatio } else { boundsH = self.parentView.frame.size.height boundsW = boundsH * videoAspectRatio } } self.customPreviewLayer.bounds = CGRectMake(0.0, 0.0, boundsW, boundsH) } }

さあ、見てみましょう -(void)setPointOfInterestInParentViewSpace:(CGPoint)point 方法。 AVFoundation フォーカスと露出の関心のあるポイントを指定できるように、カメラの自動露出とオートフォーカス機能をチェックし、座標変換を実行し、座標を検証し、AVFoundation機能を介してフォーカスを設定するメソッドの実装を次に示します。

- (void)setPointOfInterestInParentViewSpace:(CGPoint)parentViewPoint { if (!self.running) { return } // Find the current capture device. NSArray *captureDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] AVCaptureDevice *captureDevice for (captureDevice in captureDevices) { if (captureDevice.position == self.defaultAVCaptureDevicePosition) { break } } BOOL canSetFocus = [captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus] && captureDevice.isFocusPointOfInterestSupported BOOL canSetExposure = [captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose] && captureDevice.isExposurePointOfInterestSupported if (!canSetFocus && !canSetExposure) { return } if (![captureDevice lockForConfiguration:nil]) { return } // Find the preview's offset relative to the parent view. CGFloat offsetX = 0.5 * (self.parentView.bounds.size.width – self.customPreviewLayer.bounds.size.width) CGFloat offsetY = 0.5 * (self.parentView.bounds.size.height – self.customPreviewLayer.bounds.size.height) // Find the focus coordinates, proportional to the preview size. CGFloat focusX = (parentViewPoint.x - offsetX) / self.customPreviewLayer.bounds.size.width CGFloat focusY = (parentViewPoint.y - offsetY) / self.customPreviewLayer.bounds.size.height if (focusX 1.0 || focusY 1.0) { // The point is outside the preview. return } // Adjust the focus coordinates based on the orientation. // They should be in the landscape-right coordinate system. switch (self.defaultAVCaptureVideoOrientation) { case AVCaptureVideoOrientationPortraitUpsideDown: { CGFloat oldFocusX = focusX focusX = 1.0 - focusY focusY = oldFocusX break } case AVCaptureVideoOrientationLandscapeLeft: { focusX = 1.0 - focusX focusY = 1.0 - focusY break } case AVCaptureVideoOrientationLandscapeRight: { // Do nothing. break } default: { // Portrait CGFloat oldFocusX = focusX focusX = focusY focusY = 1.0 - oldFocusX break } } if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) { // De-mirror the X coordinate. focusX = 1.0 - focusX } CGPoint focusPoint = CGPointMake(focusX, focusY) if (canSetFocus) { // Auto-focus on the selected point. captureDevice.focusMode = AVCaptureFocusModeAutoFocus captureDevice.focusPointOfInterest = focusPoint } if (canSetExposure) { // Auto-expose for the selected point. captureDevice.exposureMode = AVCaptureExposureModeAutoExpose captureDevice.exposurePointOfInterest = focusPoint } [captureDevice unlockForConfiguration] }

これで、カメラを構成してフレームをキャプチャできるクラスを実装しました。ただし、フレームの構成と受信を選択するには、別のクラスを実装する必要があります。

次に、ViewControllerの定義

  1. 定義 ViewController クラスのプライベートインターフェイス。さらに、独自のクラスを定義するパブリックインターフェイスのヘッダーに依存しています。 ViewControllerビデオカメラ 。合格しましょう ViewController これらの依存関係をインポートするには、内部に次のコードを追加します。
#import #import #import #import #import #import #import 'ViewController.h' #import 'VideoCamera.h'
  1. 定義 ViewController クラスのインスタンス変数。複数使用します cv ::マット 静止画像とカメラ画像のカラーまたはグレースケール形式を保存するオブジェクト。 GUIオブジェクトには、画像ビュー、アクティビティインジケーター(ビジーローテーター)、およびツールバーが含まれます。ビデオカメラクラスのインスタンスを使用して、カメラを制御し、ビデオ画像を取得して表示します。最後に、ブール変数を使用して、ユーザーが保存ボタンを押して次のフレームを保存するかどうかを追跡します。関連する変数宣言は次のとおりです。
@interface ViewController () { cv::Mat originalStillMat cv::Mat updatedStillMatGray cv::Mat updatedStillMatRGBA cv::Mat updatedVideoMatGray cv::Mat updatedVideoMatRGBA } @property IBOutlet UIImageView *imageView @property IBOutlet UIActivityIndicatorView *activityIndicatorView @property IBOutlet UIToolbar *toolbar @property VideoCamera *videoCamera @property BOOL saveNextFrame @end

クラス名の後にはが続くことに注意してください。これは、クラスが名前を実装することを意味します。 CvVideoCameraDelegate 契約。プロトコルはOpenCVの一部であり、メソッドを定義します。 -(void)processImage:(cv :: Mat&)mat ビデオフレームの処理に使用されます。後で、[カメラの制御]セクションで、このコールバックメソッドがビデオカメラクラスにどのように関連するかについて説明します。

  1. ビデオカメラを定義する方法。一部のメソッドは、ボタンを押すなどのGUIイベントを処理するためのコールバックです。ビデオプレビューのクリックフォーカス機能、カラーまたはグレーのセグメンテーションコントロール、カメラボタンの切り替え、および保存ボタンに対して、次のコールバックを宣言しましょう。
- (IBAction)onTapToSetPointOfInterest:(UITapGestureRecognizer *)tapGesture - (IBAction)onColorModeSelected:(UISegmentedControl *)segmentedControl - (IBAction)onSwitchCameraButtonPressed - (IBAction)onSaveButtonPressed

ジェスチャインタラクションのコールバックメソッドに加えて、ビデオカメラにはいくつかのメソッドがあります。カメラの状態や画像処理の設定を変更した後、refreshメソッドを呼び出して表示を更新します。他の方法は、画像の処理、保存、共有、およびアプリケーションのビジーモードの開始と停止に役立ちます。以下は、関連するステートメントです。

- (void)refresh - (void)processImage:(cv::Mat &)mat - (void)processImageHelper:(cv::Mat &)mat - (void)saveImage:(UIImage *)image - (void)showSaveImageFailureAlertWithMessage:(NSString *)message - (void)showSaveImageSuccessAlertWithImage:(UIImage *)image - (UIAlertAction *)shareImageActionWithTitle:(NSString *)title serviceType:(NSString *)serviceType image:(UIImage *)image - (void)startBusyMode - (void)stopBusyMode
  1. ViewController.m 定義を実装する方法。まず、ファイルから静止画像をロードし、適切な形式に変換します。次に、画像ビューを使用して、プレビューの親ビューとしてビデオカメラのインスタンスを作成します。カメラにフレームをビューコントローラーに送信するように指示し(委任)、高解像度モードで30フレームでプレビューします。ここは viewDidLoad 実装:
- (void)viewDidLoad { [super viewDidLoad] UIImage *originalStillImage = [UIImage imageNamed:@'Fleur.jpg'] UIImageToMat(originalStillImage, originalStillMat) self.videoCamera = [[VideoCamera alloc] initWithParentView:self.imageView] self.videoCamera.delegate = self self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetHigh self.videoCamera.defaultFPS = 30 self.videoCamera.letterboxPreview = YES }

私たちは〜にて viewDidLayoutSubviews このメソッドは、デバイスの方向と一致するようにカメラの方向を構成します( このメソッドは、方向が変わるたびに再度呼び出されることに注意してください。 )、次のコードに示すように:

- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews] switch ([UIDevice currentDevice].orientation) { case UIDeviceOrientationPortraitUpsideDown: self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortraitUpsideDown break case UIDeviceOrientationLandscapeLeft: self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft break case UIDeviceOrientationLandscapeRight: self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeRight break default: self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait break } [self refresh] }

カメラを再構成した後、 リフレッシュ 更新方法。 リフレッシュ このメソッドは、カメラが実行されているかどうかを確認します。その場合、静止画像が非表示になっていることを確認し、カメラを停止して再起動します。それ以外の場合(カメラが実行されていない場合)、静止画像を再処理して結果を表示します。このプロセスでは、画像を適切なカラー形式に変換し、に渡します。 processImage: 方法。思い出してください、 CvVideoCamera そして私たちのもの ビデオカメラ サブクラスはビデオフレームもに渡します CvVideoCameraDelegate 契約 processImage: 方法。ここで、リフレッシュし、静止画像に同じ画像処理方法を使用します。 refreshメソッドの実装を見てみましょう。実装コードは次のとおりです。

- (void)refresh { if (self.videoCamera.running) { // Hide the still image. self.imageView.image = nil // Restart the video. [self.videoCamera stop] [self.videoCamera start] } else { // Refresh the still image. UIImage *image if (self.videoCamera.grayscaleMode) { cv::cvtColor(originalStillMat, updatedStillMatGray, cv::COLOR_RGBA2GRAY) [self processImage:updatedStillMatGray] image = MatToUIImage(updatedStillMatGray) } else { cv::cvtColor(originalStillMat, updatedStillMatRGBA, cv::COLOR_RGBA2BGRA) [self processImage:updatedStillMatRGBA] cv::cvtColor(updatedStillMatRGBA, updatedStillMatRGBA, cv::COLOR_BGRA2RGBA) image = MatToUIImage(updatedStillMatRGBA) } self.imageView.image = image } }

ユーザーがプレビューの親ビューをタップすると、 タップ に渡されたイベント setPointOfInterestInParentViewSpace: ビデオカメラに実装した方法。以下は タップ イベントに関連するコールバック:

- (IBAction)onTapToSetPointOfInterest:(UITapGestureRecognizer *)tapGesture { if (tapGesture.state == UIGestureRecognizerStateEnded) { if (self.videoCamera.running) { CGPoint tapPoint = [tapGesture locationInView:self.imageView] [self.videoCamera setPointOfInterestInParentViewSpace:tapPoint] } } }

ユーザーがいるとき セグメント化された制御 コントロールで色を選択するときは、VideoCameraを配置します グレイスケールモード (グレースケールモード)プロパティはYESまたはNOに設定されます。このプロパティはCvVideoCameraから継承されます。セットアップ グレイスケールモード その後、ViewControllerのrefreshメソッドを呼び出して、適切な設定でカメラを再起動します。これが処理するコールバックです セグメント化された制御 制御状態の変化:

- (IBAction)onColorModeSelected:(UISegmentedControl *)segmentedControl { switch (segmentedControl.selectedSegmentIndex) { case 0: self.videoCamera.grayscaleMode = NO break default: self.videoCamera.grayscaleMode = YES break } [self refresh] }

ユーザーがカメラの切り替えボタンをクリックすると、次のカメラがアクティブになるか、静止画像の初期状態にループバックします。各遷移中に、前のカメラが停止しているか、前の静止画像であることを確認する必要があります。次のカメラまたはプロセスを非表示にして起動し、次の静止画像を表示します。繰り返しになりますが、 リフレッシュ 更新方法。ボタンコールバックの実装は次のとおりです。

- (IBAction)onSwitchCameraButtonPressed { if (self.videoCamera.running) { switch (self.videoCamera.defaultAVCaptureDevicePosition) { case AVCaptureDevicePositionFront: self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack [self refresh] break default: [self.videoCamera stop] [self refresh] break } } else { // Hide the still image. self.imageView.image = nil self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront [self.videoCamera start] } }

processImage: この方法では、画像が正しく回転していることを確認し、別の名前に渡します processImageHelper: ちなみに、これはほとんどの画像処理機能を実装するのに便利な場所になります。最後に、ユーザーが[保存]ボタンをクリックすると、画像が適切な形式に変換され、に渡されます。 画像を保存: 方法。次のように:

- (void)processImage:(cv::Mat &)mat { if (self.videoCamera.running) { switch (self.videoCamera.defaultAVCaptureVideoOrientation) { case AVCaptureVideoOrientationLandscapeLeft: case AVCaptureVideoOrientationLandscapeRight: // The landscape video is captured upside-down. // Rotate it by 180 degrees. cv::flip(mat, mat, -1) break default: break } } [self processImageHelper:mat] if (self.saveNextFrame) { // The video frame, 'mat', is not safe for long-running // operations such as saving to file. Thus, we copy its // data to another cv::Mat first. UIImage *image if (self.videoCamera.grayscaleMode) { mat.copyTo(updatedVideoMatGray) image = MatToUIImage(updatedVideoMatGray) } else { cv::cvtColor(mat, updatedVideoMatRGBA, cv::COLOR_BGRA2RGBA) image = MatToUIImage(updatedVideoMatRGBA) } [self saveImage:image] self.saveNextFrame = NO } }

これまでのところ、画像処理はあまり行っておらず、色の変換と回転だけを行っています。次のメソッドを追加してみましょう。ここでは、追加の画像処理を実行して、画像をブレンドします。

- (void)processImageHelper:(cv::Mat &)mat { // TODO. }

添付:

通常、カメラのファームウェア、または少なくともそのドライバーは、キャプチャされたビデオをフラットYUV形式に効果的に変換できます。次に、アプリケーションがグレースケールデータのみを必要とする場合、Y平面を読み取ったりコピーしたりできます。この方法は、RGBフレームをキャプチャしてグレーレベルに変換するよりも効率的です。したがって、 CvVideoCameraグレイスケールモード (グレースケールモード)プロパティがYESの場合、平面YUVフレームを取得し、Y平面をに渡します。 CvVideoCameraDelegate 契約 processImage: 方法。

  1. ロードモードを開始および停止します
    写真の保存や共有で忙しいときは、アクティビティインジケーターを表示し、すべてのツールバー項目を無効にします。代わりに、写真の処理で忙しくなったら、アクティビティインジケーターを非表示にして、ツールバー項目を再度有効にします。これらの操作がGUIに影響を与える場合は、アプリケーションのメインスレッドで実行されるようにする必要があります。
    メインスレッドでロードモードを開始します。コードは次のとおりです。
- (void)startBusyMode { dispatch_async(dispatch_get_main_queue(), ^{ [self.activityIndicatorView startAnimating] for (UIBarItem *item in self.toolbar.items) { item.enabled = NO } }) }

同様に、次の方法を使用してロードモードを停止します。

- (void)stopBusyMode { dispatch_async(dispatch_get_main_queue(), ^{ [self.activityIndicatorView stopAnimating] for (UIBarItem *item in self.toolbar.items) { item.enabled = YES } }) }
  1. 画像をフォトライブラリに保存します
    ユーザーが保存ボタンを押すと、読み込みモードが開始されます。次に、カメラが実行されている場合は、次のフレームを保存する準備ができています。それ以外の場合は、処理された静止画像バージョンをすぐに保存します。イベントハンドラは次のとおりです。
- (IBAction)onSaveButtonPressed { [self startBusyMode] if (self.videoCamera.running) { self.saveNextFrame = YES } else { [self saveImage:self.imageView.image] } }

画像を保存: このメソッドは、システムとフォトライブラリ間のトランザクションを処理します。まず、PNGファイルをアプリケーションの一時ディレクトリに書き込もうとします。次に、このファイルに基づいてフォトギャラリーに写真を作成しようとします。 資産 。このプロセスの一環として、ファイルは自動的にコピーされます。他のヘルパーメソッドを呼び出して、トランザクションの成功または失敗を説明するアラートダイアログを表示します。メソッドの実装は次のとおりです。

- (void)saveImage:(UIImage *)image { // Try to save the image to a temporary file. NSString *outputPath = [NSString stringWithFormat:@'%@%@', NSTemporaryDirectory(), @'output.png'] if (![UIImagePNGRepresentation(image) writeToFile:outputPath atomically:YES]) { // Show an alert describing the failure. [self showSaveImageFailureAlertWithMessage:@'The image could not be saved to the temporary directory.'] return } // Try to add the image to the Photos library. NSURL *outputURL = [NSURL URLWithString:outputPath] PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary] [photoLibrary performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:outputURL] } completionHandler:^(BOOL success, NSError *error) { if (success) { // Show an alert describing the success, with sharing // options. [self showSaveImageSuccessAlertWithImage:image] } else { // Show an alert describing the failure. [self showSaveImageFailureAlertWithMessage:error.localizedDescription] } }] }

アラート コード:

- (void)showSaveImageFailureAlertWithMessage:(NSString *)message { UIAlertController* alert = [UIAlertController alertControllerWithTitle:@'Failed to save image' message:message preferredStyle:UIAlertControllerStyleAlert] UIAlertAction* okAction = [UIAlertAction actionWithTitle:@'OK' style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self stopBusyMode] }] [alert addAction:okAction] [self presentViewController:alert animated:YES completion:nil] }
  1. 写真を共有する
    照明が画像をフォトライブラリに保存することに成功した場合、共有オプションを備えた別のオプションをユーザーに表示します。 アラート 。次の方法では、さまざまなソーシャルメディアプラットフォームの可用性を確認し、利用可能なプラットフォームごとに1つ作成します。 アラート アクションボタン。さまざまなソーシャルメディアプラットフォームを対象としていますが、アクションボタンは互いに類似しているため、 shareImageActionWithTitle:serviceType:image: 方法。アプリのビジーモードを停止し、何もしないことを除いて、アクションを共有しないボタンも提供します。
- (void)showSaveImageSuccessAlertWithImage:(UIImage *)image { // Create a 'Saved image' alert. UIAlertController* alert = [UIAlertController alertControllerWithTitle:@'Saved image' message:@'The image has been added to your Photos library. Would you like to share it with your friends?' preferredStyle:UIAlertControllerStyleAlert] // If the user has a Facebook account on this device, add a // 'Post on Facebook' button to the alert. if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) { UIAlertAction* facebookAction = [self shareImageActionWithTitle:@'Post on Facebook' serviceType:SLServiceTypeFacebook image:image] [alert addAction:facebookAction] } // If the user has a Twitter account on this device, add a // 'Tweet' button to the alert. if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) { UIAlertAction* twitterAction = [self shareImageActionWithTitle:@'Tweet' serviceType:SLServiceTypeTwitter image:image] [alert addAction:twitterAction] } // If the user has a Sina Weibo account on this device, add a // 'Post on Sina Weibo' button to the alert. if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeSinaWeibo]) { UIAlertAction* sinaWeiboAction = [self shareImageActionWithTitle:@'Post on Sina Weibo' serviceType:SLServiceTypeSinaWeibo image:image] [alert addAction:sinaWeiboAction] } // If the user has a Tencent Weibo account on this device, add a // 'Post on Tencent Weibo' button to the alert. if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTencentWeibo]) { UIAlertAction* tencentWeiboAction = [self shareImageActionWithTitle:@'Post on Tencent Weibo' serviceType:SLServiceTypeTencentWeibo image:image] [alert addAction:tencentWeiboAction] } // Add a 'Do not share' button to the alert. UIAlertAction* doNotShareAction = [UIAlertAction actionWithTitle:@'Do not share' style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self stopBusyMode] }] [alert addAction:doNotShareAction] // Show the alert. [self presentViewController:alert animated:YES completion:nil] } - (UIAlertAction *)shareImageActionWithTitle:(NSString *)title serviceType:(NSString *)serviceType image:(UIImage *)image { UIAlertAction* action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:serviceType] [composeViewController addImage:image] [self presentViewController:composeViewController animated:YES completion:^{ [self stopBusyMode] }] } return action }