40017


横に長いUITableViewを考えています。

  1. Cellは無限に増える可能性がある
  2. UITableView(UIScrollView)のオリジナルの挙動(慣性スクロールや、バウンス)はそのまま残したい
  3. 縦横スクロールは1タップで同時に行いたい
  4. できるだけカスタムクラスは作りたくない

という要件があります。

  * オリジナルのViewを作ってそれをScrollViewに貼り付けるという方法では、1. の要件を満たすのが複雑になります。
  * ScrollViewの上にTableViewを貼り付けて、縦ScrollはTableViewに、横ScrollはScrollViewにそれぞれ担当させると、一度に一方向へしかScrollが効かず、3. が満たされません。
  * TableViewCellの中にScrollViewまたはCollectionViewを乗せても、3. が満たされませんでした。
  * ScrollView上にTableViewを乗せそれぞれのUser Interaction EnableをNOにし、さらにその上にPanGestureを設定したUIViewを乗せて、UIViewで感知した縦横のPanGestureをScrollView、TableViewそれぞれに伝達しContentOffsetを操作しましたが、2. が満たされませんでした。

UITableViewの横スクロール問題はよく話題になるかとは思いますが、最善の(最も簡単で、オリジナルUIを継承しつつ、デバイス不可も少ない)方法はどのようなものなのでしょうか。  
もし成功されている方がいらっしゃいましたら、アドバイスをお願いいたします。

* * *

回答してくださった皆さんのおかげで解決することができました。  
結論としては、UICollectionViewを使用し、UICollectionViewLayoutを自作することですべての要件を満たすことができました。  
以下、UICollectionViewLayoutのobjcのコードを転載しておきます。

[code]

    #import "BidirectionalCollectionLayout.h" // UICollectionViewLayoutを継承
    
    @implementation BidirectionalCollectionLayout {
        NSInteger cellCount;
        CGSize cellSize;
    }
    
    - (void)prepareLayout
    {
        [super prepareLayout];
        cellCount = [self.collectionView numberOfItemsInSection:0];
        cellSize = CGSizeMake(2000.0f, 66.0f);
    }
    
    - (CGSize)collectionViewContentSize {
        return CGSizeMake(cellSize.width, cellCount * cellSize.height);
    }
    
    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        NSArray *indexPathsArray = [self indexPathsForItemsInRect:rect];
        NSMutableArray *array = [NSMutableArray array];
        for (NSIndexPath *indexPath in indexPathsArray) {
            [array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
        }
        return array;
    }
    
    - (NSArray *)indexPathsForItemsInRect:(CGRect)rect
    {
        NSMutableArray *array = [NSMutableArray array];
    
        NSInteger minRow = MAX(0, (NSInteger)floor(rect.origin.y / cellSize.height));
        NSInteger maxRow = MIN(cellCount, (NSInteger)ceil((rect.origin.y + rect.size.height) / cellSize.height));
        for (NSInteger i = minRow; i < maxRow; i++) {
            [array addObject:[NSIndexPath indexPathForItem:i inSection:0]];
        }
        return array;
    }
    
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        CGFloat offsetY = indexPath.item * cellSize.height;
        CGRect frame = CGRectMake(0, offsetY, cellSize.width, cellSize.height);
        attributes.frame = frame;
        return attributes;
    }
    
    @end
    
[/code]


CC BY-SA 3.0
2017-12-04T02:12:00.730
40012
2017-12-05T14:04:15.393
2017-12-05T14:04:15.393
8501
8501
question
ios objective-c uitableview uicollectionview
UITableViewに横スクロールを実装する最良の方法


※コメントで指摘いただきましたが、下記方法では上下方向のスクロールができません。お目汚し失礼しました。

CGAffineTransformを利用するとViewを回転して表示することができます。  
UITableViewを90度回転させて表示すれば目的の通りになると思います

[code]

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        CGRect bounds = [[UIScreen mainScreen] bounds];
    
        UITableView *tblView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
    
        CGAffineTransform rotate90 = CGAffineTransformMakeRotation(-M_PI / 2.0);
        [tblView setTransform:rotate90];
        tblView.frame = bounds;
        [self.view addSubview:tblView];
    
    }
    
[/code]

ただし、UITableViewCellの中身も回転してしまうので、逆回転したビューをCellの上に配置してあげる必要があります。

\---追記---  
以下URLのようにUICollectionViewを使う方法もあるようです。  
<https://qiita.com/koyopro/items/b4363cfd7f0838f88103>

\---追記---  
参照先がSwiftだったのでCocoaでのサンプルコードも載せておきます。

[code]

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        CGRect bounds = [[UIScreen mainScreen] bounds];
    
        UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    
        layout.itemSize = CGSizeMake(50, 100);
        layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
        layout.minimumLineSpacing = 0.0;
        layout.minimumInteritemSpacing = 0.0;
        layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
        UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:bounds collectionViewLayout:layout];
    
        [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([UICollectionViewCell class])];
    
        collectionView.frame = CGRectMake(0, 0, bounds.size.width, 100);
        [self.view addSubview:collectionView];
        //クラスに<UICollectionViewDataSource>を設定しておいてください
        collectionView.dataSource = self;
    }
    
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
    {
        return 20;
    }
    
    - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        NSInteger row = indexPath.row;
    
        UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([UICollectionViewCell class]) forIndexPath:indexPath];
    
        if(cell == nil)
        {
        }
        cell.backgroundColor = [UIColor colorWithRed:row * 20 / 255.0 green:1.0 blue:0.0 alpha:1.0];
    
        return cell;
    }
    
[/code]


CC BY-SA 3.0
2017-12-04T02:51:23.123
40013
2017-12-04T06:36:02.237
2017-12-04T06:36:02.237
17014
17014
40012
answer


上記コメントで書きましたが、UITableViewを使って実現するのはなかなか難しいのではないかと思います。

「ScrollView上にTableViewを乗せ〜」で、かつそのViewへのタッチイベントを全て補足してそれぞれのビューをスクロールさせる方法なども一応考えられますが、UITableViewやUIScrollViewと同じ手触りのスクロールを実現するには相応の苦労が伴うと思います。

他の方法として、近い機能を提供するOSSライブラリを利用することが考えられます。

例えば、以下のライブラリはiOS上にMSエクセルの様なスプレッドシートを表示することができます。

**kishikawakatsumi/SpreadsheetView**  
<https://github.com/kishikawakatsumi/SpreadsheetView>

UITableViewとは勝手が違う部分もあるでしょうが、縦横へのスクロールは可能になると思います。

* * *

**追記**

コメントで指摘されたとおり、UICollectionViewで実現可能でした。  
以下、縦横へスクロールするUICollectionViewLayoutの簡易実装例です。  
セルサイズ固定で、単一セクションのみ対応など必要最低限以下の実装ですので、  
あくまで参考程度にお願いします。  
より実用的なLayoutの実装には[UICollectionViewLayout](https://developer.apple.com/documentation/uikit/uicollectionviewlayout)のドキュメント等を参考にしてください。  
（Swiftで失礼。適宜Objective-Cへ読み替えてください）

[code]

    import UIKit
    
    // 縦横スクロールレイアウトの超簡易実装
    // セルサイズ固定、単一Sectionのみ対応、ヘッダー・フッター非対応、セルの追加削除への考慮なし
    class SampleLayout: UICollectionViewLayout {
    
        let cellSize = CGSize(width:1000, height: 60);
    
        private var _contentSize = CGSize.zero
    
        override var collectionViewContentSize: CGSize {
            return _contentSize
        }
    
        override func prepare() {
            super.prepare()
            let num = self.collectionView!.numberOfItems(inSection: 0)
            self._contentSize = CGSize(width: cellSize.width, height: cellSize.height * CGFloat(num))
        }
    
        override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            let attr = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
            attr.frame = CGRect(origin: CGPoint(x:0, y: CGFloat(indexPath.item) * cellSize.height), size: cellSize)
            return attr
        }
    
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            var attrs:[UICollectionViewLayoutAttributes] = []
    
            let begin = max(0, Int(floor(rect.minY / cellSize.height)))
            let end   = min(collectionView!.numberOfItems(inSection: 0), Int(ceil(rect.maxY / cellSize.height)))
    
            for item in (begin..<end) {
                attrs.append(layoutAttributesForItem(at: IndexPath(item: item, section: 0))!)
            }
    
            return attrs
        }
    }
    
[/code]


CC BY-SA 3.0
2017-12-04T06:54:01.457
40017
2017-12-04T08:05:26.727
2017-12-04T08:05:26.727
23829
23829
40012
answer


次のような感じでいかがでしょうか？  
最初からテーブルで表示される要素のサイズがわかっている必要があります。  
スクロールビュー上にテーブルビューを置くのは一緒ですが、  
スクロール量をUIViewのPanイベントでとるのではなく、スクロールビューとテーブルビューのスクロール量を一致させるのがコツです。  
セルが横方向にも並ぶのであれば、もう少し改造する必要があります。

[code]

    #import "ViewController.h"
    
    @interface ViewController ()<UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate>
    {
        UIScrollView *scrView;
        UITableView *tblView;
    
        int rowNum;
        CGSize rowSize;
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        rowNum = 100;
        rowSize = CGSizeMake(1000, 70);
    
        CGRect bounds = [[UIScreen mainScreen] bounds];
    
        scrView = [[UIScrollView alloc] initWithFrame:bounds];
    
        float contentViewHeight = rowSize.height * rowNum;
    
        //行数が少ない時は縦Bounceのために、スクロールビューを1px大きくする
        if(contentViewHeight <= bounds.size.height)
        {
            contentViewHeight = bounds.size.height+ 1;
        }
    
    
        tblView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, rowSize.width, bounds.size.height)];
    
        tblView.userInteractionEnabled = NO;
        tblView.delegate = self;
        tblView.dataSource= self;
        tblView.rowHeight = rowSize.height;
        [self.view addSubview:tblView];
    
        scrView.delegate = self;
        [scrView setContentSize:CGSizeMake(rowSize.width, contentViewHeight)];
        scrView.backgroundColor = [UIColor clearColor];
        [self.view addSubview:scrView];
    }
    
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return rowNum;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        if(cell == nil)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
            UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 1000, 44)];
    
            lbl.tag = 1;
    
            [cell.contentView addSubview:lbl];
        }
    
        UILabel *label = [cell viewWithTag:1];
    
        label.text = [NSString stringWithFormat:@"%dABCDEFGHIJKLMUOPQRSTUVWXYZABCDEFGHIJKLMUOPQRSTUVWXYZABCDEFGHIJKLMUOPQRSTUVWXYZABCDEFGHIJKLMUOPQRSTUVWXYZABCDEFGHIJKLMUOPQRSTUVWXYZABCDEFGHIJKLMUOPQRSTUVWXYZABCDEFGHIJKLMUOPQRSTUVWXYZ", (int)indexPath.row];
    
        NSLog(@"%d", (int)indexPath.row);
    
        return cell;
    }
    
    -(void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
        //下地のスクロールビューと上に乗っているテーブルビューのクスロール量を合わせる
        tblView.contentOffset = CGPointMake(scrView.contentOffset.x, scrView.contentOffset.y);
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    
    
    }
    
    
    @end
    
[/code]

**編集1**  
対処療法的ですが、行数が少なくても縦Bouncesが効くようにしました。  
小手先感が否めないので、CollectionViewで対応できるならそちらで対応する方が自然だと思います。  
**編集2**  
スクロールバーが出てなかったので、出るように調整してみました。


CC BY-SA 3.0
2017-12-04T07:19:56.490
40019
2017-12-05T02:04:14.260
2017-12-05T02:04:14.260
17014
17014
40012
answer