「Objective-C」カテゴリーアーカイブ

【Objective-C】UIPageViewControllerでUIPageControlが表示されない

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

UIPageViewControllerを使った時に、下記の条件を満たせば自動的にUIPageControl(iPhoneのホーム画面にもある、現在位置を示す○)が表示されるらしい。

・UIPageViewControllerを生成するとき、nitWithTransitionStyle:UIPageViewControllerTransitionStyleScrollになっている。

    self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                               navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                               options:nil];

※self.pageViewControllerは、UIPageViewControllerです。

・ページが戻ったときに呼ばれるハンドラ

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    .....
{

・ページが進んだときに呼ばれるハンドラ

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    .....
{

【参考】
UIPageViewControllerの画面下部に表示されているPageControlを隠す – iOSアプリ開発の逆引き辞典
UIPageViewControllerの使い方 -Tips- – hyoromoのブログ

が、実際に自分で作ってみたら、何も表示されない。散々いろんなケースを疑い、果てはないなら自分でUIPageControlを実装してみたりして試行錯誤した結果、わかったことは・・・

「UIPageControlは表示されているが見えないだけ」

であったことが判明。

UIPageControlはざっくりいうと、

・親ViewにUIPageViewControllerを生成して、subViewとして追加。
・追加されたUIPageViewController.viewに、実際に表示する子Viewを入れ替えることで切替を表示する。

という仕組みになっている。

で、UIPageControlも親Viewに表示されるはずですが、親ViewのbackgroundColorが白(whiteColor)になっていたことが原因でした。
UIPageControlの初期値についての情報は得られなかったのですが、色々試した限りでは。。。

・[UIPageControl currentPageIndicatorTintColor](現在ページの○)>白
・[UIPageControl pageIndicatorTintColor](選択されてないページの○)>白+アルファ値(50%?)
・[UIPageControl backgroundColor](背景色)>白

となっているようです。よって親ViewのbackgroundColorが白だと何も見えない。ただ、あるはずの場所をタップすると動きます(見えないのでタップが可能ということ自体に気づいたのもだいぶ後になってからですが…)。

要するに色を替えて見えるようにしてやればいいけど、UIPageViewについてググっても、UIPageView+UIScrollViewをセットで使うときの話題ばかり。で、ようやく見つけたのがコレ。UIPageViewControllerの中にあるUIPageControlを参照すればいいようです。

    //ページコントロール(色を変更する)
    UIPageControl* proxy = [UIPageControl appearanceWhenContainedIn:[self.pageViewController class], nil];
    
//    NSLog(@"setPageIndicatorTintColor >> %@",[proxy pageIndicatorTintColor]);
//    NSLog(@"currentPageIndicatorTintColor >> %@",[proxy currentPageIndicatorTintColor]);
//    NSLog(@"backgroundColor >> %@",[proxy backgroundColor]);
    
    [proxy setPageIndicatorTintColor:[UIColor lightGrayColor]];
    [proxy setCurrentPageIndicatorTintColor:[UIColor blackColor]];
    [proxy setBackgroundColor:[UIColor whiteColor]];

※self.pageViewControllerは、UIPageViewControllerです。

ちなみにコメントになっているNSLogでデフォルトの色を確認しようとしたのだけれど、(null)しか返ってきませんでした。何でだろう?

【参考】
storyboard – iOS Page based template, regarding UIPageViewController and UIPageControl color of the dots – Stack Overflow

これでようやくUIPageControlが表示されました。左右スクロールによる表示制御やタップしたときの挙動は、デフォルトで設定されるようで、UIPageControlについて書いたコードは、上の色替えの部分のみです。

更に、デフォルトだとUIPageControlの位置がかなり下になるので、UIPageViewController.viewの高さを削ってやると、もうちょっと上の位置になります。

- (void)viewDidLoad
{
    ....
    // Change the size of page view controller
    self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 30);
    ....
{

最後に一応、コードを載せておきます。
ここでは親クラス(HelpViewController)と子クラス(DetailViewController)を使っています。
子クラス(DetailViewController)には・・・

@property NSUInteger pageIndex;
@property NSString *titleText;
@property NSString *captionText;
@property NSString *imageFile;

のプロパティがあって、生成時に表示する情報を渡しています。

・HelpViewController.h


#import <UIKit/UIKit.h>
#import "DetailViewController.h"

@interface HelpViewController : UIViewController<UIPageViewControllerDataSource,UIPageViewControllerDelegate>

@property (strong, nonatomic) UIPageViewController *pageViewController;
@property (strong, nonatomic) NSArray *pageTitles;
@property (strong, nonatomic) NSArray *pageImages;

@end

・HelpViewController.m

#import "HelpViewController.h"

@interface HelpViewController ()
{
    NSMutableArray *captionArray;
    NSMutableArray *imageArray;
    NSInteger currentPageIndex;
}

@end

@implementation HelpViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    //----
    
    //タイトル設定
    
    self.navigationItem.title = NSLocalizedString(@"conf_use", NULL);
    
    // テキストを設定する
    captionArray = [[NSMutableArray alloc] init];
    [captionArray addObject:NSLocalizedString(@"use_caption1", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption3", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption4", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption5", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption6", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption7", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption8", NULL)];
    [captionArray addObject:NSLocalizedString(@"use_caption9", NULL)];
    
    //
    // 画像ファイル名名を設定する
    imageArray = [[NSMutableArray alloc] init];
    [imageArray addObject:NSLocalizedString(@"help_img01", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img03", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img04", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img05", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img06", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img07", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img08", NULL)];
    [imageArray addObject:NSLocalizedString(@"help_img09", NULL)];

    // UIPageViewControllerを生成
    self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                               navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                               options:nil];
    
    self.pageViewController.dataSource = self;
    self.pageViewController.delegate = self;
    
    DetailViewController *startingViewController = [self viewControllerAtIndex:0];
    NSArray *viewControllers = @[startingViewController];
    [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
    
    // UIPageViewControllerのサイズを変更(UIPageControlを上に表示する為)
    self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 30);
    
    [self addChildViewController:_pageViewController];
    [self.view addSubview:_pageViewController.view];
    [self.pageViewController didMoveToParentViewController:self];
    
    //ページコントロール(色を変更する)
    UIPageControl* proxy = [UIPageControl appearanceWhenContainedIn:[self.pageViewController class], nil];
    
    [proxy setPageIndicatorTintColor:[UIColor lightGrayColor]];
    [proxy setCurrentPageIndicatorTintColor:[UIColor blackColor]];
    [proxy setBackgroundColor:[UIColor whiteColor]];
    
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (DetailViewController *)viewControllerAtIndex:(NSUInteger)index
{
    if (([captionArray count] == 0) || (index >= [captionArray count])) {
        return nil;
    }
    
    // 新しいページのviewを生成
    DetailViewController *DetailViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"DetailViewController"];
    DetailViewController.imageFile = imageArray[index];
    DetailViewController.captionText = captionArray[index];
    DetailViewController.titleText = NSLocalizedString(@"conf_use", NULL);

    DetailViewController.pageIndex = index;
    
    return DetailViewController;
}

#pragma mark - Page View Controller Data Source

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSUInteger index = ((DetailViewController*) viewController).pageIndex;
    
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }
    
    index--;

    return [self viewControllerAtIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    NSUInteger index = ((DetailViewController*) viewController).pageIndex;
    
    if ((index >= [captionArray count]) || (index == NSNotFound)) {
        return nil;
    }
    
    index++;
    if (index == [captionArray count]) {
        return nil;
    }
    
    return [self viewControllerAtIndex:index];
}

- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
    return [captionArray count];
}

- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
    return 0;
}

-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    if(completed){
        DetailViewController *vc =[self.pageViewController.viewControllers lastObject];
        currentPageIndex =  vc.pageIndex;
    }
}


@end

・DetailViewController.h

#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController
    @property (weak, nonatomic) IBOutlet UITextView *caption;
    @property (weak, nonatomic) IBOutlet UIImageView *helpImage;

@property NSUInteger pageIndex;
@property NSString *titleText;
@property NSString *captionText;
@property NSString *imageFile;

@end

・DetailViewController.m

#import "DetailViewController.h"

@interface DetailViewController ()

@end

@implementation DetailViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _helpImage.image = [UIImage imageNamed:self.imageFile];
    self.title = self.titleText;
    _caption.text = self.captionText;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

あまりに単純な話ですが、同じような目に遭う人もいるかもしれないのでメモとして。

【Objective-C】アプリ内保存した画像のパスがpathForResourceで取れない(解決編)

【追記】2014.9.13 3:00
この方法はあくまで「アプリ内保存した画像のパス」の話で、Xcode上で最初から登録している画像には当てはまりません。パスに「Documents」ディレクトリが含まれないためです。

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

前回の解決編です。

JBOYSOFT JunjiSuzukiさんに「NSSearchPathForDirectoriesInDomains を使うのもいいかも」との助言を受けて、描いてみたコードがこれ。NSSearchPathForDirectoriesInDomainsでディレクトリのパスを取ってきて、保存しているファイル名と組み合わせて、自前でパスを作っている。

    NSArray *paths = NSSearchPathForDirectoriesInDomains(
                                                         NSDocumentDirectory,
                                                         NSUserDomainMask, YES);
    
    NSArray *ph = [select_name componentsSeparatedByString:@"/"];
    NSString *fn = [ph objectAtIndex:ph.count-1];
    
    NSLog(@"paths >> %@",paths);
    NSLog(@"fn >> %@",fn);
    
    path = [NSString stringWithFormat:@"%@/%@",[paths objectAtIndex:0],fn];
    
    NSLog(@"path >> %@",path);

    iconImage = [[UIImage alloc] initWithContentsOfFile:path];

これだと既存のものも、起動後に保存したものも、どちらも問題なく参照出来る!NSLogによるトレースはこちら。

paths >> (
    "/var/mobile/Applications/BB4E2366-816D-427F-8F8B-FF836EB58AB8/Documents"
)
fn >> icon20140913003532.png
path >> /var/mobile/Applications/BB4E2366-816D-427F-8F8B-FF836EB58AB8/Documents/icon20140913003532.png

気になったので、前回描いたコードで取得されるパスを確認してみる。前回のコードはコレ。

    NSArray *phrases = [imageName componentsSeparatedByString:@".png"];
    NSString *filename = [phrases objectAtIndex:0];
    NSString *path= [[NSBundle mainBundle] pathForResource:filename ofType:@"png"];
    
    //アプリ内保存した直後の画像は、path が nullになる
    NSLog(@"path >> %@",path);
    
    //保存直後の画像はNSBundleでパスが取れないための対処(アプリが再起動しないとダメみたい)
    UIImage *iconImage;
    
    if (path == NULL) {
        iconImage = [UIImage imageNamed:imageName];
        
    } else {
        iconImage = [[UIImage alloc] initWithContentsOfFile:path];
    }

※ imageNameには、”../Documents/icon20140912171442.png”のようなパスが入っている。

アプリ起動前に保存されている画像について、NSLogで表示されるパスを見ると・・・

path >> /var/mobile/Applications/BB4E2366-816D-427F-8F8B-FF836EB58AB8/xxxxxx.app/../Documents/icon20140913003017.png

と、アプリ名(xxxxxx.app/../)がはさまってる。この違いは何?NSBundleだとこうなるの?

更に、冒頭の修正コードをアプリ名(xxxxxx.app/../)がはさまってるパスに変更して参照してみる。

    NSArray *paths = NSSearchPathForDirectoriesInDomains(
                                                         NSDocumentDirectory,
                                                         NSUserDomainMask, YES);
    
    NSArray *ph = [select_name componentsSeparatedByString:@"/"];
    NSString *fn = [ph objectAtIndex:ph.count-1];
    
    NSLog(@"paths >> %@",paths);
    NSLog(@"fn >> %@",fn);
    
    NSArray *paths2 = [[paths objectAtIndex:0] componentsSeparatedByString:@"Documents"];
    path = [NSString stringWithFormat:@"%@iconFaceCam.app/%@",[paths2 objectAtIndex:0],select_name];
    
    NSLog(@"path >> %@",path);

    iconImage = [[UIImage alloc] initWithContentsOfFile:path];

NSLogの出力結果がこれ。

paths >> (
    "/var/mobile/Applications/BB4E2366-816D-427F-8F8B-FF836EB58AB8/Documents"
)
fn >> icon20140913012801.png
path >> /var/mobile/Applications/BB4E2366-816D-427F-8F8B-FF836EB58AB8/xxxxxx.app/../Documents/icon20140913012801.png

動作としては、こちらでも問題なく動きました。

で、結論としては、アプリ内保存のファイル参照では・・・

・NSSearchPathForDirectoriesInDomainsでドキュメントディレクトリを取得する。
・ドキュメントディレクトリ+ファイル名でパスを生成する。
・生成したパスを使って、initWithContentsOfFileで参照する。

ということになるんだけれど、そもそも何で・・・

[[NSBundle mainBundle] pathForResource:filename ofType:@”png”]

でパスが取得できないのか、がわからずモヤモヤします…。

とりあえずは余計なifとか要らなくなったので、よしとして先に進みます。
JBOYSOFT JunjiSuzukiさんに感謝。

【参考】
iOS でデータを永続化する方法 – A Day In The Life

【Objective-C】アプリ内保存した画像のパスがpathForResourceで取れない

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

以前、iOSアプリ内に画像を保存するという記事を書いて、その後、保存した画像の参照を[UIImage imageNamed]から、[[NSBundle mainBundle] pathForResource:]に変更したら、保存直後の画像参照ができなくなった。

で、いろいろテストしてみると、一度アプリを終了してからの画像は参照出来るので、NSBundleはアプリ起動時の情報しか持っていないのではないかと思われます。とりあえず動かすために、こんなコードを書いて回避してるんだけど、NSBundleを強制的に更新するとか、何かいい方法はないでしょうか?NSBundleを調べてみたけどよくわからなかったので。

    NSArray *phrases = [imageName componentsSeparatedByString:@".png"];
    NSString *filename = [phrases objectAtIndex:0];
    NSString *path= [[NSBundle mainBundle] pathForResource:filename ofType:@"png"];
    
    //アプリ内保存した直後の画像は、path が nullになる
    NSLog(@"path >> %@",path);
    
    //保存直後の画像はNSBundleでパスが取れないための対処(アプリが再起動しないとダメみたい)
    UIImage *iconImage;
    
    if (path == NULL) {
        iconImage = [UIImage imageNamed:imageName];
        
    } else {
        iconImage = [[UIImage alloc] initWithContentsOfFile:path];
    }

※ imageNameには、”../Documents/icon20140912171442.png”のようなパスが入っている。

【参考】
NSBundleクラス | Second Flush

【Objective-C】特定のUIGestureRecognizerを削除する

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

一時的にUIGestureRecognizerをセットして、不要になったら削除するという場合のメモ。例えば・・・

・UITapGestureRecognizerはデフォルトで常に設定
・ある条件で、UIPanGestureRecognizerを追加
・不要になったら、UIPanGestureRecognizerだけを削除

というような場合。

    for(UIGestureRecognizer *gesture in [selectIconView gestureRecognizers]) {
        NSLog(@" isClass UITapGestureRecognizer!! %d", [gesture isKindOfClass:[UITapGestureRecognizer class]]);
        NSLog(@" isClass UIPanGestureRecognizer!! %d", [gesture isKindOfClass:[UIPanGestureRecognizer class]]);
    }

※ selectIconViewは、UIGestureRecognizerを設定したUIImageView。

こんな感じで「isKindOfClass」で判定可能。

で、実際にやりたかったのは、こんな感じ。

    for(UIGestureRecognizer *gesture in [selectIconView gestureRecognizers]) {
        
        // UIPanGestureRecognizerのみ削除
        if([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
            [selectIconView removeGestureRecognizer:gesture];
        }
    }

【参考】
aideal.jp UIGestureRecognizer
gesture recognizerを全削除する – 日々精進
UIGestureRecognizerクラス | Second Flush
Objective-C – UIViewからUIGestureRecognizerを削除 – Qiita
isKindOfClassとisMemberOfClassってなにが違うねん | S4U -smile for you-

【Objective-C】UITableViewCellに入れたUISwitchから親セルを参照

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

UITableViewCellをカスタマイズして、UISwitchを入れてみた。これはそんなに難しくない。こんな感じで。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    
    // cellを選択不可にする
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    
    // UISwitchを作成
    UISwitch *sw = [[UISwitch alloc] initWithFrame:CGRectZero];
    
    // UISwitchのタップ動作を指定
    [sw addTarget:self action:@selector(tapSwich:) forControlEvents:UIControlEventTouchUpInside];
    
    // UISwitchをaccessoryViewに代入
    cell.accessoryView = sw;
    
    cell.textLabel.text = sw_name;
    
    return cell;
}

【参考】
[XCODE] UITableViewCellに、様々なコントロール要素をのせて表示する方法 – YoheiM.NET
UITableViewCell の accessoryView を使うと少し楽 (フェンリル | デベロッパーズブログ)

で、UISwitchをタップしたときに呼ばれるtapSwichで「何番目のセルか?」(indexPath.row)を取得しようとしてハマった。
最初は「UITableViewCellに入れたんだから、その親でしょ?」とこんな感じに書いたけど取得できず。

    UITableViewCell *cell = (UITableViewCell *)[sw superview];
    int row = (int)[self.tableView indexPathForCell:cell].row;

そこで階層を調べるために、こんなふうに書いてみた。

    NSObject *t = (UITableViewCell *)[sw superview];
    NSObject *tt = (UITableViewCell *)[[sw superview] superview];
    NSObject *ttt = (UITableViewCell *)[[[sw superview] superview] superview];
    NSObject *tttt = (UITableViewCell *)[[[[sw superview] superview] superview] superview];
    
    NSLog(@"--switch  >> %@ , \n%@ , \n%@, \n%@",t,tt,ttt,tttt);

その出力結果がコレ(見やすいように一部省略)

2014-09-02 01:50:00.953 iconFaceCam[3017:60b] --switch  >> 
<UITableViewCellScrollView: ...> , 
<UITableViewCell: ...> , 
<UITableViewWrapperView: ...>, 
<UITableView: ...>

UISwitchとUITableViewCellの間に、UITableViewCellScrollViewというのが入ってるらしい。
そこで、こんな感じに書いて参照出来ました。

-(void)tapSwich:(id)sender
{
    UISwitch *sw = (UISwitch *)sender;
    
    UITableViewCell *cell = (UITableViewCell *)[[sw superview] superview];
    int row = (int)[self.tableView indexPathForCell:cell].row;
    
    // UISwitch:初期値を指定
    switch (row) {
        case 0:
            .....;            
            break;
        case 1:
            .....
            break;
        case 2:
            break;
            
        default:
            break;
    }
}

調べてみたら、この階層はiOS7になって変更されているらしいので、iOS6ではまた違うらしい。
詳しくは以下のリンクへ。

iOS7 では UITableViewCell の subviews階層が変更されている | objc-Lovers

【Objective-C】日付を24時間表示で取得する際の注意点

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

日付を24時間表示で取得するには、LocaleをUSに設定しないと端末設定で結果が変わるらしい。

で、こんな感じで関数を書いてみた。

//現在の年月日時分秒を取得
-(NSString *)getCurrentTime
{
    NSDate *nowdate = [NSDate date];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyyMMddHHmmss"];
    
    //24時間表示にするためLocaleはUSにする
    [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"US"]];
    NSString *dateString = [formatter stringFromDate:nowdate];
    
    return dateString;
}

【参考】
NSDateで現在の日付を取得する | イリテク株式会社
NSDateFormatter – 文字列と日付の変換 – 強火で進め
日本語環境では、NSDateFormatterでフォーマットした日付がおかしい – 24/7 twenty-four seven

【Objective-C】ModalとNavigationの画面遷移方法の違い

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

Storyboardの画面リンク(Segue)を使わずに、コードで画面遷移を書いていてハマったのでメモ。

・ナビゲーション(Navigation/UITableViewControllerなどで使う、NavigationBarが出てくる画面遷移)

    //画面を遷移(※confVCは表示するViewCOntroller)
    [self.navigationController pushViewController:confVC animated:YES];

    //元画面に戻る
    [self.navigationController popViewControllerAnimated:YES];

・モーダル(Modal/現在のViewに重なるように表示する画面遷移)

    //画面を遷移(※confVCは表示するViewCOntroller)
    [self presentViewController:configurationVC animated:YES completion:nil];

    //元画面に戻る
    [self dismissViewControllerAnimated:YES completion:nil];

この2つをごっちゃにしていてハマりました。どのViewControllerでも、ナビゲーション(Navigation)の方法ならきちんとNavigationBarが表示されて戻るボタンが自動でつきます。

最初はUITableViewControllerだけNavigationBarが出なくて調べていたら、こんな基本的なことだたっという・・。

【参考】
iOS アプリの画面開発の基礎を理解する – A Day In The Life
iPhoneプログラミング入門「モーダルビュー1」
Objective-C – モーダルビューを出したり消したり – Qiita
yuki0n0 高校生アプリ開発者のブログ: storyboardで作った画面をコードで画面遷移

だんだん作り込んでくると、基本的な画面のレイアウト(固定部分)はStoryboardを使って、画面遷移やボタン配置など可変になる部分はコードで制御する方がいいかなと思っていて、Storyboardの画面リンク(Segue)を全部削除してコードで書き換えてます。

【Objective-C】NSStringを特定の文字列で分割して配列化

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

まあ、ActionScriptでいうところの、String.split(“/”)をやりたかったのですが、意外に探すのに手こずったのでメモ。

// ファイルパスを「/」で分割して、削除ファイル名を取り出す
NSString *test = @"../Documents/icon20140819002020.jpg";
NSArray *phrases = [test componentsSeparatedByString:@"/"];
NSString *filename = [phrases objectAtIndex:phrases.count-1];
NSLog(@" --delete file >> %@",filename);

で、結果がこちら。

--delete file >> icon20140819002020.jpg

参考:YoheiM.NET|[XCODE] NSStringを特定の文字で分割する方法

【Objective-C】UICollectionViewCellの選択表示

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

UICollectionViewCellで選択表示をさせようと思ってハマったのでメモ。

参考:Collection View プログラミングガイド(iOS用)【PDF】
※P25〜P28あたり。

選択状態をコードで制御するには・・・

たとえば、セルの選択状態をアプリケーション側で描画したい場合は、selectedBackgroundViewプロパティをnilにし、デリゲートオブジェクトに、外観を変更する処理を組み込んでください。
collectionView:didSelectItemAtIndexPath:メソッドに外観を変更する処理、
collectionView:didDeselectItemAtIndexPath:メソッドに元の状態に戻す処理を実装します。

ということらしいので、コードを書いてみた。

//UICollectionViewCell:編集
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.selectedBackgroundView = nil;
    
    //(中略)

    return cell;
}

//
-(void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選択した %@",indexPath);
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor redColor];
}

-(void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選択解除した %@",indexPath);
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
}

これだとタップしている間は、背景色が赤に変わるけれど、指を離すと白に戻ってしまう。

で、更に資料を読んでみると・・・

図 2-3に、未選択状態のセルにユーザがタッチしたときに発生する、一連の動作を示します。最初の「タッチダウン」イベントを受けて、コレクションビューはセルの「強調表示」状態をYESにします。もっとも、自動的にセルの外観に反映されるわけではありません。その後、「タッチアップ」イベントが発生すると、「強調表示」状態はNOに戻り、「選択」状態はYESに変わります。ユーザが選択状態を変更すると、コレクションビューはセルのselectedBackgroundViewプロパティに設定されているビューを表示します。コレクションビューが関与してセルの外観を変えるのは、この場合に限ります。ほかの視覚効果はデリゲートオブジェクトが行わなければなりません。

つまり、指を離すと選択状態にはなるものの didUnhighlightItemAtIndexPathが呼ばれて、表示が戻ってしまう・・・ということらしい。

結局、didHighlightItemAtIndexPathとdidUnhighlightItemAtIndexPathは使わず、以下の様に書いて解決。

//UICollectionViewCell:編集
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1" forIndexPath:indexPath];
    
    //通常の背景
    UIView* backgroundView = [[UIView alloc] initWithFrame:cell.bounds];
    cell.backgroundColor = [UIColor whiteColor];
    cell.backgroundView = backgroundView;
    
    //選択時の背景
    UIView* selectedBGView = [[UIView alloc] initWithFrame:cell.bounds];
    selectedBGView.backgroundColor = [UIColor redColor];
    cell.selectedBackgroundView = selectedBGView;
    
    //(中略)
    
    return cell;
}

最初のコードで、選択時のイベントハンドラ(didSelectItemAtIndexPath)で選択済表示に変えることもできるけど、それだと選択済項目の表示を戻してやる処理が必要になるので、単一選択ではこれが一番簡単かなと。

【Objective-C】UIBarButtonItemの右ボタンを動的に差し替える

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

カメラロールでサムネイル表示したときに、右上に出る「選択」>「キャンセル」の切替と同じやつです。これってViewは切り換えてないよなあと思ったので、ボタンの差し替えをテストしてみました。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    //削除ボタンを追加
    UIBarButtonItem *delButton = [[UIBarButtonItem alloc]
                                  initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
                                                                               target:self
                                                                               action:@selector(delButtonDidPush:)];
    

    self.navigationItem.rightBarButtonItem = delButton;
}

-(void)delButtonDidPush:(UIBarButtonItem *)button
{
    NSLog(@" -- trash!!");
    
    //
    UIBarButtonItem *canelButton = [[UIBarButtonItem alloc]
                                    initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                    target:self
                                    action:@selector(cancelButtonDidPush:)];
    self.navigationItem.rightBarButtonItem = canelButton;
}

-(void)cancelButtonDidPush:(UIBarButtonItem *)button
{
    NSLog(@" -- cancel!!");
    
    //削除ボタンを追加
    UIBarButtonItem *delButton = [[UIBarButtonItem alloc]
                                  initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
                                  target:self
                                  action:@selector(delButtonDidPush:)];
    
    
    self.navigationItem.rightBarButtonItem = delButton;
    
}

これでちゃんと動いてるし、タップのイベントも片方しか出てこないので、たぶんいいはず。
UIToolBarについては、こんな記事が。

なんとなく|UINavigationController の UIToolBar で Button 表示を切り替えたいメモ

たぶんnavigationItem.rightBarButtonItemは1つしか置けないので、重なったりすることはないのでは?と推測。