iOS嵌ったメモ

残しておかないと二度嵌りそうな実装のメモ。

もっといい解決方法があれば教えてください。

随時更新しますので、内容が変わります。

# エラーログがでなくなった、謎のエラーがでるようになった

iOSシュミレーターを切り替えたりしていると、シュミレーター側にゴミが残るのかいろいろ不具合が発生する。

* iOSシュミレーターを選択
* ツールバーのiOSシュミレーターを選択
* コンテンツと設定をリセットを選択
* リセットボタンを選択

これでアプリを起動し直すといろいろ解決したりする。

# スクロールしないUIWebViewを使用したい

UIWebViewのスクロールをNOにして使用する場合に困るのが高さの通知です。
オブザーバー等で無理やり高さの通知を行うと、無限ループする可能性があるので、JavaScriptから一方的に高さを通知して、UIWebViewの高さを広げる方法がいいと思います。

```html
<html>
<body>
....
</body>
<script type="text/javascript">
<!--
    // ドキュメントのサイズを監視して、変更があればそれを通知する
    var offsetHeight = document.documentElement.offsetHeight;
    var documentObserver = function () {
        if (offsetHeight != document.documentElement.offsetHeight) {
            offsetHeight = document.documentElement.offsetHeight;
            location.href = "local://fix-size";
        }
        setTimeout(documentObserver, 200);
    };
    $(document).ready(function () {
        documentObserver();
    });
-->
</script>
</body>
</html>
```

```objc
// ロード開始前
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];
    if ([url.scheme isEqualToString:@"local"] && [url.host isEqualToString:@"fix-size"]) {
        [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.offsetHeight"].floatValue;
        [self mas_updateConstraints:^(MASConstraintMaker *make) {
            make.height.equalTo(@(height));
        }];
        return NO;
    }
}

```

# UIScrollViewでAutoLayoutを使用して再描画するとスクロール内のコンテンツの位置がおかしくなる
描画の位置がおかしい。
Appleのバグらしい。

描画前に一度スクロール位置を0にするとよい。

参考: http://stackoverflow.com/questions/15345522/uiscrollview-wrong-offset-with-auto-layout
一番票が入っているやり方はよくわからんかった。。

```objc:一度スクロール位置を0にする

@implementation MyUIViewController

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // スクロールビューの再描画の位置がずれるので、スクロール位置を一度0にする
    self.tempContentOffset = self.scrollView.contentOffset;
    self.scrollView.contentOffset = CGPointZero;
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // スクロール位置をもとに戻す
    self.scrollView.contentOffset = self.tempContentOffset;
}

@end
```

# UIScrollViewでUIButtonを使いたい
UIScrollViewにUIButtonを置くとボタンをタップできなかったり、ボタンの上でスクロールできなかったりする。

touchesShouldCancelInContentViewをオーバーライドしたサブクラスを作成する。
delaysContentTouchesをNOに設定する。

```objc:カスタムのUIScrollViewを作成する
@interface MyUIScrollView : UIScrollView
@end

@implementation MyUIScrollView

-(BOOL)touchesShouldCancelInContentView:(UIView *)view
{
    return YES;
}

@end

@implementation MyUIViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // カスタムUIScrollViewを作成
    self.scrollView = MyUIScrollView.new;
    self.scrollView.delaysContentTouches = NO;
    [self.view addSubview:self.scrollView];
}

@end

```

# 可変する高さのUITableViewCellでAutoLayoutを使用した場合に制約エラーが発生する
高さが可変のセルでdequeueReusableCellWithIdentifierでセルを使い回した場合に、
前のセルの高さが次のセルのAutoLayoutの制約を満たせない場合は、AutoLayoutのエラー出力される。

これはcell.contentViewのバグらしい。
contentViewにautoresizingMaskを設定することで解決する。

参考: http://qiita.com/sintario/items/67e9d73f10f0653df655

```objc:contentViewにautoresizingMaskをつける
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
    }
    return self;
}
```

# 可変する高さのテーブルビューセルで高さを計算する場合

レイアウト専用のセルを作成している場合は、現在描画しているものと横幅が変わっている場合があるので、横幅を設定してから計算する。

```objc
// レイアウト用のセル
// heightで計算する前に横幅を設定する
- (void)setCellWidth:(CGFloat)cellWidth
{
    CGRect bounds = self.bounds;
    bounds.size.width = cellWidth;
    self.bounds = bounds;
}
```

# UICollectionViewでpagingEnabledしたい

UIScrollを継承しているのでpagingEnabledを設定できますが間にスペースを入れれない問題が発生します。
そこでUICollectionViewFlowLayoutを継承してあるメソッドをオーバーライドすることによってページ遷移させることができるようになります。

元ネタは以下のURLのDarthMike氏の回答です。
http://stackoverflow.com/questions/13492037/targetcontentoffsetforproposedcontentoffsetwithscrollingvelocity-without-subcla


```objc

@interface CollectionViewPageFlowLayout: UICollectionViewFlowLayout
@end

// CollectionViewでページングできるようにするレイアウトクラスです。
@implementation CollectionViewPageFlowLayout

// ビューの横幅 (ビュー本体+スペース)
- (CGFloat)pageWidth {
    return self.itemSize.width + self.minimumLineSpacing;
}

// ページングできるように拡張
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    CGFloat rawPageValue = self.collectionView.contentOffset.x / self.pageWidth;
    CGFloat currentPage = (velocity.x > 0.0) ? floor(rawPageValue) : ceil(rawPageValue);
    CGFloat nextPage = (velocity.x > 0.0) ? ceil(rawPageValue) : floor(rawPageValue);

    // ビューの位置の半分まできた場合、またはフリックの速度が早い場合は、ページ遷移する
    BOOL pannedLessThanAPage = fabs(1 + currentPage - rawPageValue) > 0.5;
    BOOL flicked = fabs(velocity.x) > [self flickVelocity];
    if (pannedLessThanAPage && flicked) {
        proposedContentOffset.x = nextPage * self.pageWidth;
    } else {
        proposedContentOffset.x = round(rawPageValue) * self.pageWidth;
    }
    return proposedContentOffset;
}

// 強制的にページ遷移するフリック速度
- (CGFloat)flickVelocity {
    return 0.3;
}

@end
```

# UICollectionViewのreloadDataで変なアニメーションが入る場合の対処

reloadDataにアニメーションが入っているらしいので、強制的に止める。

```objc
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
```

# QuickDialogでUIを操作する

UI操作のみで済む場合は直接操作で、ラベル等書き換えるとかセルを更新する理由がある場合はそちらで対処する。

```objc
boolElement.boolValue = NO;
if (boolElement.controller) {
    QuickDialogController *quickDialogController = (QuickDialogController *)boolElement.controller;
    UITableViewCell *cell = [quickDialogController.quickDialogTableView cellForElement:boolElement];
    
    // 直接操作
    [((UISwitch *)cell.accessoryView) setOn:boolElement.boolValue animated:YES];

    // セルを更新
    [quickDialogController.quickDialogTableView reloadCellForElements:boolElement, nil];
}
```

# UIGestureRecognizerのサブクラスを作成したい

UIGestureRecognizerSubclassをインポートしておくとUIGestureRecognizerStateがreadwriteになったりする。

```objc
#import <UIKit/UIGestureRecognizerSubclass.h>
```

# テーブルビューを複数並べてページングしたい

UITableViewを継承して複数のジェスチャーを受け入れるようにする

```objc
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}
```

UITableViewを複数貼り付けるUIScrollViewにある一定まで横移動しなければ反応しないようにするジェスチャーを設定する。

```objc
MyPanBlockGestureRecognizer *panBlockGestureRecognizer = [[MyPanBlockGestureRecognizer alloc] initWithInView:self.view];
    panGestureRecognizer.delegate = self;
    [self.pagingScrollView addGestureRecognizer:panBlockGestureRecognizer];
    [self.pagingScrollView.panGestureRecognizer requireGestureRecognizerToFail:panGestureRecognizer];
```

# UIViewに直書きする場合はNSStringDrawingUsesFontLeadingを使用しないと正しく計算されない

それから横幅が少しはみ出て計算される場合があるので、最後に補正するとよい。
さらにparagraphStyle.hyphenationFactorを0.0より大きくしないとlineHeightがおかしくなる。

```objec

NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineHeightMultiple = 1.3;
paragraphStyle.hyphenationFactor = 1.0;

NSAttributedString *text = [NSAttributedString alloc] initWithString:@"test" attributes:@{NSParagraphStyleAttributeName: paragraphStyle}];
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine;

// サイズを計算する
CGRect drawRect = [attributedString boundingRectWithSize:size, options:options context:nil];

// 指定した横幅より大きくなる場合があるので戻す
if (drawRect.size.width > size.width) {
    drawRect.size.width = size.width;
}

// 描画する
[attributedString drawWithRect:drawRect options:options context:nil];
```


# AFHTTPOperationManagerのoutputStreamを変更する場合にメモリリークする

openをつけてあげると、解放してくれるようになる。

参考: https://github.com/AFNetworking/AFNetworking/issues/2306

```objc
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation.outputStream open];
```

# SDWebImageで取得してきた画像をキャッシュした場合に、メモリにある画像とキャッシュ画像のサイズが違う

保存する画像名に画面サイズ3xとかが必要らしいです。
めんどうなので全部につけましょう。

```objc
CGFloat scale = [UIScreen mainScreen].scale;
NSString *cacheKeyScale = [NSString stringWithFormat:@"@%.0fx.hax", scale];
[SDWebImageManager sharedManager].cacheKeyFilter = ^NSString *(NSURL *url) {
    NSString *key = [[url absoluteString] stringByAppendingString:cacheKeyScale];
    return key;
};
```

# UIProgressViewのアニメーションを制御する・完了を待つ

```objc
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
    [self.progressView setProgress:1.0 animated:NO];
    [self.progressView layoutIfNeeded];
} completion:^(BOOL finished) {
    [UIView animateWithDuration:0.3 animations:^{
        self.progressView.alpha = 0.0;
    }];
}];
```