● Carbon視点でiPhone探求(2010/06/27)

  この記事は、MOSAの会員にのみ読むことができるデベロッパー向けの
  ウェブサイトMOSADeN Onlineのに掲載された記事です。ほぼ一ヶ月
  遅れでここに掲載されて行きます。

  〜 コピー&ペーストを実装する 〜


今回は、前回手が回らなかった対称画像の「コピー」機能にチャレンジします。加えて「ペースト」機能にも対応し、クリップボードに保存されている画像を画像一覧へ登録できるようにしたいと思います。

対称画像のコピー処理はImageViewController.mに実装します。まずはシステムからの編集処理の要求に応える準備をします。最初に、ImageViewControllerをファーストレスポンダ(FirstRespondr)として認識してもらえるように、canBecomeFirstResponderメソッドをオーバライドしてYESを返すようにします。また、どんな編集処理に対応できるかを示すには、canPerformAction:withSender:メソッドで処理を選別します。今回はコピー処理にしか対応しませんので、actionセレクターの内容がcopy:の場合にだけYESを返しています。これにより編集時に表示されるメニューの項目は「コピー」に限定されることになります。

- (BOOL)canBecomeFirstResponder
{
  return YES;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{  
  if( action==@selector(copy:) ) // コピーにのみ対応
    return YES;
  return NO;
}

実際の処理ルーチンはアクションメソッド(IBAction)と同等の実装形式です。ここでは、前回作成したgetSymmetryImageメソッドを使い対称画像を得てから、それをシステム標準クリップボードのimageプロパティ(UIImage)にセットします。クリップボードへ画像データを渡す処理はこれで終わりです。大変簡単ですね!

- (void)copy:(id)sender
{
  UIImage    *image;
  
  if( image=[self getSymmetryImage] )
    [UIPasteboard generalPasteboard].image=image; // 画像のコピー
}


続いて編集メニューを表示させる処理を実装します。メニューは画像をしばらくタップしていると(1秒間の長押し)ポップアップするようにします。ImageViewController.hにタップ位置を保存するim_point(CGPoint)を、またメニューが表示されているかどうかの状態を保存するためのim_copy(BOOL)プロパティを用意しておきます。

CGPoint      im_point;   // タップ位置を保存
BOOL       im_copy;   // 「コピー」メニューを表示中

実際のコピーメニューの表示は以下のhandleLongTapメソッドで実行します。メニューの表示場所には、タップ位置を保存したim_pointを使います。そして、メニューが表示されたことを示すためにim_copyにYESを設定します。

- (void)handleLongTap  //  1秒後にコピーメニューを表示する
{
  UIMenuController  *theMenu;
  CGRect       drt;
  
  [self becomeFirstResponder];
  if( theMenu=[UIMenuController sharedMenuController] )
  {
    drt=CGRectMake( im_point.x,im_point.y,0,0 );
    [theMenu setTargetRect:drt inView:self.view];  // 表示位置の設定
    [theMenu setMenuVisible:YES animated:YES];  // メニュー表示
    im_copy=YES;                 // メニューが表示された
  }
}

では、 handleLongTapをどんなタイミングで呼び出せば良いのでしょうか?結論としては直に呼び出すのではなく、タッチ開始イベントハンドラのtouchesBegan:withEvent:メソッド内でperformSelector:withObject:afterDelay:メソッドを用いて呼び出し(実行)を予約します。 afterDelayに1.0を設定することで、実行はタップ開始から1秒後の予約ということになります。よってタップした指が1秒以内にスクリーンから離れたら、この予約をキャンセルする必要があります。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event // タッチ開始
{  
  UITouch    *touch;
  NSUInteger  ct;

  im_copy=NO;  // コピーメニューは表示前
  touch=[touches anyObject]; // 最初のタッチオブジェクト  
  im_point=[touch locationInView:self.view]; // タップ位置保存
  
  ct=[[touches anyObject] tapCount];
  if( ct >=2 )    //  ダブルタップでリセット
  {
    if( im_type.selectedSegmentIndex <=1 ) // 上下
    {
      im_view0.sy_offset=im_view1.sy_offset=0.0;
      [im_view0 setNeedsDisplay];
      [im_view1 setNeedsDisplay];
    }
    else                  // 左右
    {
      im_view2.sy_offset=im_view3.sy_offset=0.0;
      [im_view2 setNeedsDisplay];
      [im_view3 setNeedsDisplay];
    }
  }
  else  
    [self performSelector:@selector(handleLongTap) withObject:nil afterDelay:1.0];
           // ロングタップ処理ハンドラ予約
}

タッチ移動中のイベントハンドラの最初の部分に、予約キャンセル処理を記述します。まず、im_copyがYESなら既にメニューが表示されているので、対称画像のオフセット値の決定処理は実行しません。そのまま移動処理から抜けてしまいます。それ以外の場合は、最初にcancelPreviousPerformRequestsWithTarget:selector:object:メソッドで予約したhandleLongTap(コピーメニュー表示)の呼び出しをキャンセルします。同じく、タッチ終了イベントのハンドラtouchesEnded:withEvent:メソッドでも予約キャンセルを行います。これで画像のコピー処理の実装は終了です。

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event // 移動中
{
  CGPoint    vpt1,vpt2;
  CGFloat    off;

  if( im_copy==YES )  // メニュー表示中は何も実行しない
    return;
    
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongTap) object:nil];
         // ロングタップ処理中止

  ・・・・・・・・・
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event  // タッチ終了
{
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongTap) object:nil];
}

  

続いてクリップボードに保存された画像を「画像一覧」に登録する操作です。まず、それぞれの処理で得られた画像をModelオブジェクトに登録するために、Document.mに新規にaddModelWithImage:メソッドを用意します(繰り返し使う部分を抜き出した)。

- (Model *)addModelWithImage:(UIImage *)image   // モデルの作成(画像添付)
{  
  Model  *model;
  
  if( model=[self addModelWithName:@"Photo" type:@"Image"] )
  {
    model.md_image=resizeUIImage( 1,image,40,40 ); // サムネイル画像を作成
    [model saveImage:image]; // 画像ファイルを保存
    [self save];        // ドキュメント保存
  }
  return model;
}

続いて「+」ボタン機能(アクションシート)の拡張です。システム標準クリップボードに保存された画像はimageプロパティ(UIImage)から得られます。よって「+」ボタンで表示されるアクションシートに「ペースト」ボタンを追加し、それがタップされたら、UIImagePickerControllerの写真アルバムやカメラロールから画像を得る代わりに、システム標準のペーストボードから画像を得ます。もしペーストボードに画像が保存されていたら、先ほどの addModelWithImage:でModelオブジェクトへ登録し「画像一覧」を更新します。

- (IBAction)addImage:(id)sender
{
  NSString    *b1,*b2,*b3,*b4=nil;
  NSString    *esc,*title;
  UIActionSheet  *action;

  esc=NSLocalizedString( @"ESC_BUTN",@"" );    // キャンセル
  title=NSLocalizedString( @"ACT_TYPE_01",@"");  // タイトル
  b1=NSLocalizedString( @"ACT_TYPE_02",@"" );    // 写真アルバム
  b2=NSLocalizedString( @"ACT_TYPE_03",@"" );    // カメラロール
  b3=NSLocalizedString( @"ACT_TYPE_04",@"" );    // ペースト
  if( [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]==YES )  //  デバイスにカメラあり
    b4=NSLocalizedString( @"ACT_TYPE_05",@"" );  //  撮影
  if( action=[[UIActionSheet alloc] initWithTitle:title delegate:self cancelButtonTitle:esc destructiveButtonTitle:nil otherButtonTitles:b1,b2,b3,b4,nil] )
  {
    action.actionSheetStyle=UIBarStyleBlackTranslucent;  // スタイル設定
    [action showInView:self.view];  // 方法選択のアクションシートを開く
    [action release];
  }
}

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
  UIImagePickerController  *picker;
  UIImage          *image;
  NSUInteger        type;

  if( buttonIndex!=[actionSheet cancelButtonIndex] ) // キャンセルではない
  {
    if( buttonIndex==2 ) // ペースト
    {
      if( image=[UIPasteboard generalPasteboard].image ) // ペーストボードをチェック
      {
        [[self document]addModelWithImage:image]; // モデル作成(画像添付)
        [self.tableView reloadData]; // テーブルビューを描画し直す
      }
    }
    else
    {
      if( buttonIndex==0 )    // 写真アルバムから画像を読み込む
        type=UIImagePickerControllerSourceTypePhotoLibrary;
      else if( buttonIndex==1 )  // カメラロールから像を読み込む
        type=UIImagePickerControllerSourceTypeSavedPhotosAlbum;
      else if( buttonIndex==3 )  // 撮影を行う
        type=UIImagePickerControllerSourceTypeCamera;

      if( picker=[[UIImagePickerController alloc] init] )
      {
        picker.allowsEditing=YES; // 画像編集(トリミング)を許す
        picker.sourceType=type; // 画像ピッカーのタイプを設定
        picker.delegate=self;   // デリゲートはこのオブジェクト
        [self presentModalViewController:picker animated:YES]; 
                    // 画像ピッカーをオープンする
        [picker release];
      }
    }
  }
}

  

次回は、しんぶんしアプリの「環境設定」(User Default)を用意します。このアプリでは環境設定で設定するようなオプションがあまり思いつかないのですが(笑)何か無理やり捻出してみましょう。

copyright 2010 Ottimo, Inc. All rights reserved
無断転載・引用禁止