黒毛和牛モモバラ切り落し100g298円

iPhoneアプリを作ってます。リリースノートとか用ブログです。

ラノベル-1.1.16をリリースしました

ラノベル-1.1.16をリリースしました。
iOS10で一覧画面が消えるバグの対応をしています。
なのですが、ちょいちょいクラッシュしているようなので今直しています。
手元の環境で再現できていないのですが、一覧でリロードしてる間に小説を開いて、(話数が減るなどで)消えた話にアクセスすると落ちるはずです。
申し訳有りませんが、安定するまで少々お待ち下さい。

ラノベル-1.1.15をリリースしました

先ほどラノベルの新バージョンをリリースしました。主にバグ修正です。
合わせて利用規約を明示するように審査で指摘されましたので、アプリ初回起動時(バージョンアップ後も含む)に規約が出るようになっています。同意を押すと普段の画面に戻り以降は表示されません。(規約改定する際にまた表示されると思います。)
また、どの時点になるか未定ですがiOS7の対応はもうじきやめようと思っております。

あとコメントの方あまりお返事できておらず申し訳ないです。
割と多くご依頼を頂いておりますカクヨム対応については今のところ予定しておりません。個人的には対応したいのですが、なろうと互換性のないサイトを対応するのはちょっと難しい現状です。
ファイル共有とバックアップ機能については検討中です。iPhone6sに変えた際に無いと不便だなとしみじみと思いましたが、喉元過ぎれば熱さ忘れるで全然作業が捗っておりません。
諸々申し訳ございませんが、よろしくお願い致します。

Xcode7 で NSFetchedResultsControllerDelegate がクラッシュする場合の対応方法

Xcode7-beta の頃から NSFetchedResultsControllerDelegate で落ちるようになりました。現在の(betaではない)Xcode7 でも同様にクラッシュするようです。私のアプリも一時期結構落ちてましてユーザーさんにはご迷惑をおかけしておりました。

当初 iOS9 固有の問題かと思っていたのですが iOS8 でも発生しているようで、iOS 起因の問題なのか Xcode 起因の問題なのかちゃんと把握しておりませんが、条件さえ揃えば確実にクラッシュしますので結構クリティカルな問題と認識しています。

いくつかのクラッシュする状態があるようですが、NSFetchedResultsChangeUpdate 時に [UITableView reloadRowsAtIndexPaths:withRowAnimation:] を呼び出すと落ちるようになったのが私の方では一番のクラッシュ原因でした。話の流れは Apple の forum や stackoverflow が詳しいのでそちらをご覧ください。

なお私が問題に遭遇した時点では、Apple の forum や stackoverflow にあった解決方法ではクラッシュが解決しなかったので、以下のようなコードで対応しました。

まず FetchedResultsControllerDelegate というクラスを用意しています。Xcode の新規プロジェクトで master/detail 型のプロジェクトを作成すると [configureCell:atIndexPath:] が UITableViewDataSource に追加されますが、私のアプリは master/detail 型として作成したものではありませんし NSFetchedResultsControllerDelegate は同じクラスを使い回していますので別オブジェクトにしてしまった方が便利かと思います。(Swift の場合は適当に変換してください。)

FetchedResultsControllerDelegate.h

#import <CoreData/CoreData.h>

@protocol FetchedResultsControllerDelegateDataSource <UITableViewDataSource>

@required
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;

@end

@interface FetchedResultsControllerDelegate : NSObject <NSFetchedResultsControllerDelegate>

@property (weak, nonatomic) UITableView *tableView;

- (id)initWithTableView:(UITableView *)tableView;

@end

FetchedResultsControllerDelegate.m

#import "FetchedResultsControllerDelegate.h"

@implementation FetchedResultsControllerDelegate

- (id)initWithTableView:(UITableView *)tableView
{
    self = [super init];
    if (self) {
        self.tableView = tableView;
    }
    return self;
}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeUpdate:
            break;
        case NSFetchedResultsChangeMove:
            break;
    }
}

- (BOOL)canUpdateWithTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath
{
    NSObject <UITableViewDataSource> *dataSource = tableView.dataSource;
    
    if (dataSource == nil || ![dataSource conformsToProtocol:@protocol(FetchedResultsControllerDelegateDataSource)]) {
        return NO;
        
    } else {
        NSInteger sections = 1;
        if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
            sections = [dataSource numberOfSectionsInTableView:tableView];
        }
        if (indexPath.section >= sections) {
            return NO;
        }
        NSInteger rows = 0;
        if ([dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
            rows = [dataSource tableView:tableView numberOfRowsInSection:indexPath.section];
        }
        return (indexPath.row < rows);
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;
    
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeUpdate:
            if ([self canUpdateWithTableView:tableView indexPath:indexPath]) {
                NSObject <FetchedResultsControllerDelegateDataSource> *dataSource = (NSObject <FetchedResultsControllerDelegateDataSource> *)tableView.dataSource;
                UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
                if (cell != nil) {
                    [dataSource configureCell:cell atIndexPath:indexPath];
                }
            }
            break;
            
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

@end

あくまでも参考として利用される場合は、NSFetchedResultsChangeUpdate の分岐で configureCell を呼び出せない場合の判定を忘れないようにしてください。うろ覚えですが Xcode6 の頃は [cellForRowAtIndexPath:] で取得できない位置のセルに対して NSFetchedResultsChangeUpdate が発生することは無かったように思うのですが、Xcode7 では発生するようですので、(未確認ですが)AppleNSFetchedResultsControllerDelegate の実装例でも落ちるような気がします。

上記クラスを使うには、まず UITableViewDataSource で以下のように NSFetchedResultsController の delegate に FetchedResultsControllerDelegate オブジェクトを指定し、

- (void)someMethod {
     FetchedResultsControllerDelegate *fetchedResultsControllerDelegate = [[FetchedResultsControllerDelegate alloc] initWithTableView:self.tableView];
     NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
 }

[configureCell:atIndexPath:] を定義し、[tableView:cellForRowAtIndexPath:] からもそれを呼び出すようにします。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier forIndexPath:indexPath];
     [self configureCell:cell atIndexPath:indexPath];
     return cell;
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    // セルの初期化処理
}

ラノベル-1.1.14をリリースしました

一括リロード時に落ちるバグの修正バージョンです。
バージョン1.1.10前後からのバグ修正はこれにて一区切りしたかと思います。
取り急ぎご連絡ということで、不具合等ございましたらご連絡ください。

ラノベル-1.1.13をリリースしましたが、一覧更新時に問題が発生するようです

和暦バグ等を修正したバージョンをリリースしました。
一覧画面更新処理にバグがあるようで、更新にセットした状態でリロードすると落ちることがあるようです。
アクセス等でリロードして頂くと落ちませんので、落ちる場合は次バージョンまでそちらで操作頂ければと思います。
申し訳ありませんが現在修正中ですのでしばらくお待ち願います。

ラノベル-1.1.12をリリースしました

本日ラノベル-1.1.12がリリースされてました。
アプリが公開されるとAppleからメールが来るのですが今回来なかったっぽいので、何時ごろリリースされたのか把握してなかったりしてますが、以下の機能追加等を行っております。基本的にはバグ修正リリースになります。

  • ActionExtensionを追加しました。Safari等の他アプリからラノベルを開けます。(キャプチャの左下のアイコンです)

f:id:wagyu298:20151026222856p:plain

  • 和暦に関するバグを修正しました。
  • iPhone6/6+(s)等で発生するUIバグを修正しました。

バックグラウンド時にコピーされたURLから小説を開く機能がiOS9で動作しないトラブルについては、iOS9の制限のようですので修正ができませんでした。他アプリから小説を開く場合はActionExtensionから起動願います。

あと和暦のバグは一応動作確認してますがちゃんと直ってなかったらすみません。

ラノベル-1.1.11をリリースしました

ラノベル-1.1.10にiOS7でランキング/検索画面を開くと落ちるバグがあったため、今朝方修正しAppStoreに緊急審査依頼を行っておりましたが、先ほど審査が終わりましてリリース準備状態になりました。あと1時間ぐらいでAppStoreに公開されると思います。
このバージョンは上記のバグのみを修正したものです。iOS8以降でご利用の場合は特に変更はありません。