【Swift 3】UICollectionViewのセル上に乗せた画像をピンチで拡大する【UICollectionView】

どもも。

UICollectionViewを用いてiPhoneのデフォルト写真アプリのようなものを作成しています。イメージビューアーっていうのかなそんな感じのやつです。

やりたいこととしては、UICollectionView上のUIImageViewの拡大となります。

こんな感じ。

ちなみにこの拡大だけで2日くらい行き詰まってました(´・ω・`)

UICollectionViewはUIScrollViewのサブクラス!

UICollectionViewはUIScrollViewのサブクラスになります。

UIScrollViewならviewForZooming(in:)を使えば簡単に拡大することができます。

 

どうにかこれを使いたいのですが、viewForZooming(in:)がそもそも呼ばれない。原因がわからず色々プロジェクト外で試行錯誤してたんですが、ようやく解決方法がわかったので紹介していきます。

 

UICollectionViewを設置しよう

適当にコレクションビューを設置します。

この辺は普通に設置するだけでおkです。

 

まずはデリゲートを書いておきます。

class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{
    /* ~~  */
}

設置します。

class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{
    private var myCollectionView:UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let width = self.view.frame.width
        
        //レイアウト
        let imageLayout = UICollectionViewFlowLayout()
        imageLayout.itemSize = CGSize(width:width,height:width * 4 / 3)
        //スクロールの向き
        imageLayout.scrollDirection = .horizontal
        imageLayout.minimumLineSpacing = 0
        imageLayout.minimumInteritemSpacing = 0
        imageLayout.sectionInset = UIEdgeInsetsMake(0,0,0,0)
        
        
        //コレクションビュー
        myCollectionView = UICollectionView(frame: CGRect(x:0,y:49,width:width,height:width * 4 / 3), collectionViewLayout: imageLayout)
        
        //デリゲートをつける
        myCollectionView.delegate = self
        myCollectionView.dataSource = self
        
        //カスタムセルを指定
        myCollectionView.register(SingleViewCell.self, forCellWithReuseIdentifier: "singleCell")
        
        //スクロールバーを表示するかどうか
        myCollectionView.showsHorizontalScrollIndicator = false
        myCollectionView.showsVerticalScrollIndicator = false
        
        myCollectionView.backgroundColor = UIColor.gray
        
        //ページングをするかどうか
        myCollectionView.isPagingEnabled = true
        self.view.addSubview(myCollectionView)
        

    }

    /********************* CollectionView *********************/
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("タップ")
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        //セルの設定
        let cell:SingleViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "singleCell", for: indexPath) as! SingleViewCell
        
            let image = UIImage(named:"picture.jpg")
            cell.imageView.image = image
        
        return cell
    }
}

 

適当に同じ画像を10枚横スクロールで表示しています。

 

カスタムセルを作ろう

UICollectionViewCellのサブクラスを作成します。

File > New > File

 

 

Source > Cocoa Touch Class > Next

 

Sublclass of:をUICollectionViewCellにして作成

 

この中にScrollViewを設置して、さらにその上にImageViewを置きます。

import UIKit

class SingleViewCell: UICollectionViewCell,UIScrollViewDelegate {
    public var imageView:UIImageView!
    public var scrollView:UIScrollView!
    
    required init(coder aDecoder:NSCoder){
        super.init(coder: aDecoder)!
    }
    
    override init(frame:CGRect){
        super.init(frame:frame)
        
        //スクロールビューを設置
        scrollView = UIScrollView()
        scrollView.frame = CGRect(x:0,y:0,width:self.frame.width,height:self.frame.height)
        
        //デリゲートを設定
        scrollView.delegate = self
        
        //最大・最小の大きさを決める
        scrollView.maximumZoomScale = 4.0
        scrollView.minimumZoomScale = 1.0
        
        self.contentView.addSubview(scrollView)
        
        //imageViewを生成
        imageView =  UIImageView()
        imageView.frame = CGRect(x:0,y:0,width:self.frame.width,height:self.frame.height)

        //scrollViewにのせる
        scrollView.addSubview(imageView)
        
    }
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        //実際に拡大したいscrollViewのサブビューを返す
        return self.imageView
    }
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        print("zoomおわり")
    }
    
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        print("zoomはじまり")
    }
}

 

これで無事拡大ができます。

ダブルタップでの拡大を記事の最後に付け足しました。

 

失敗ポイント

躓いた原因はいくつかあります(´・ω・`)

・UICollectionViewがUIScrollViewのサブクラスだということで、自動的にviewForZooming(in:)が呼ばれると思っていた。

・UIScrollViewを設置してデリゲートをつけると、自動的にviewForZooming(in:)が呼ばれると思っていた。

・そもそもUICollectionView上のスクロールビューを拡大しても意味がないことに気がついていなかった。

などなど。

 

まずviewForZooming(in:)が呼ばれなかった原因はこちら。

        //最大・最小の大きさを決める
        scrollView.maximumZoomScale = 4.0
        scrollView.minimumZoomScale = 1.0

このzoomに関する設定をしていなかったから呼ばれなかったわけです。

setZoomScale(scale:animated:)だとかzoom(to:animated:)だとかを書いてもviewForZooming(in:)は呼ばれていました。

この辺がないと呼ばれない。ここに気づくまでが時間かかりました・・・疲れてたのかな・・・・
気づいてからはすぐでした。

 

そしてUICollectionView上のスクロールビューを拡大しようとしていたので、枠だけが大きくなって画像が大きくならないという。すぐに気づいたけどね・・・?

ちなみに、UICollectionViewを使っている場合はUIScrollViewDelegateではなくUICollectionViewDelegateを使ってください。ちゃんとscrollviewのも含まれているよ(`・ω・´)

 

あとは色々UIGestureRecognizerを用いてピンチしようとしたのですが、色々競合しちゃったのかうまくいきませんでした。そしてごちゃごちゃしてるし煩雑です。

 

ダブルタップで拡大機能もつけようと思っているので、またすぐ記事書くかもです〜。

ほぼ写しただけで済んでしまったので、ダブルタップで拡大を追加したコード載せておきます。

参考はこちら「画像をダブルタップとピンチイン・ピンチアウトで拡大・縮小する

変なところにひっかかってなかったらこの記事だけで実装できてたな・・・(´・ω・`)

import UIKit

class SingleViewCell: UICollectionViewCell,UIScrollViewDelegate {
    public var imageView:UIImageView!
    public var scrollView:UIScrollView!
    
    required init(coder aDecoder:NSCoder){
        super.init(coder: aDecoder)!
    }
    
    override init(frame:CGRect){
        super.init(frame:frame)
        
        //スクロールビューを設置
        scrollView = UIScrollView()
        scrollView.frame = CGRect(x:0,y:0,width:self.frame.width,height:self.frame.height)
        
        //デリゲートを設定
        scrollView.delegate = self
        
        //最大・最小の大きさを決める
        scrollView.maximumZoomScale = 4.0
        scrollView.minimumZoomScale = 1.0
        
        self.contentView.addSubview(scrollView)
        
        //imageViewを生成
        imageView =  UIImageView()
        imageView.frame = CGRect(x:0,y:0,width:self.frame.width,height:self.frame.height)
        scrollView.addSubview(imageView)
        
        let doubleTap = UITapGestureRecognizer(target:self,action:#selector(SingleViewCell.doubleTap(gesture:)))
        doubleTap.numberOfTapsRequired = 2
        imageView.isUserInteractionEnabled = true
        imageView.addGestureRecognizer(doubleTap)
        
    }
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return self.imageView
    }
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        print("zoomおわり")
    }
    
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        print("zoomするよ")
    }
    
    // ダブルタップ
    func doubleTap(gesture: UITapGestureRecognizer) -> Void {
       // if ( self.scrollView.zoomScale < self.scrollView.maximumZoomScale ) {
         if ( self.scrollView.zoomScale < 3 ) {
            let newScale:CGFloat = self.scrollView.zoomScale * 3
            let zoomRect:CGRect = self.zoomRectForScale(scale: newScale, center: gesture.location(in: gesture.view))
            self.scrollView.zoom(to: zoomRect, animated: true)
            
        } else {
            self.scrollView.setZoomScale(1.0, animated: true)
        }
    }
    // 領域
    func zoomRectForScale(scale:CGFloat, center: CGPoint) -> CGRect{
        var zoomRect: CGRect = CGRect()
        zoomRect.size.height = self.scrollView.frame.size.height / scale
        zoomRect.size.width = self.scrollView.frame.size.width / scale
        
        zoomRect.origin.x = center.x - zoomRect.size.width / 2.0
        zoomRect.origin.y = center.y - zoomRect.size.height / 2.0
        
        return zoomRect
    }
}

 

 

ではでは。

 

どうでもいいけど一番最初のUICollectionViewはUIScrollViewのサブクラスっていうとこ、いらなくない・・・?

 

2018/04/14追記 gistに↑age↑ヾ(`・ω・´)ノ シャキーン
Swift4に対応したものをGistにあげておきました〜。


ついでに最近のコードの書き方に変えておきました。
今の方が見やすいと信じている。
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS
more