概要
Instagramアプリのようなパン/ピンチ操作を受け取り、拡大/移動ができるイメージビューを作成する方法です。 サンプルでは、パン操作とピンチ操作を使用していますが、同じ方法で回転(Rotation gesture)することもできます。
Interface Builder構築手順
1.フィルタービューの追加
イメージビューの下の階層に透明なビューを用意します。 イメージビューが操作を受け取り拡大率に応じて、フィルタービューの背景色を濃くすることで画像を見やすくします。
2.イメージビューの追加
フィルタービューの上の階層にイメージビューを配置します。 User Interaction EnabledとMultiple Touchにチェックをします。
3.パン/ピンチジェスチャーの追加
Pan Gesture RecognizerおよびPinch Gesture Recognizerをイメージビューの上にドラッグ&ドロップします。 また、delegateおよびIBActionをViewControllerにセットします。
4.パンジェスチャーのプロパティ
1本指では、パン操作を受け付けたくない場合には、Touchesを2に変更します。
プログラム説明
TransformProperty構造体
イメージビューの変形状態を保持します。
fileprivate struct TransformProperty { private let kMaxBackgroundAlpha: CGFloat = 0.77 private let kMinBackgroundAlpha: CGFloat = 0.4 var point: CGPoint var scale: CGFloat var backgroundAlpha: CGFloat { didSet { // Round the value backgroundAlpha = min(kMaxBackgroundAlpha, max(kMinBackgroundAlpha, backgroundAlpha)) } } init() { point = CGPoint(x: 0, y: 0) scale = 1.0 backgroundAlpha = kMinBackgroundAlpha } }
ViewControllerとIBAction
onPinchGestureとonPanGestureで操作を受け取り、イメージビューを変形します。
class ViewController: UIViewController { @IBOutlet fileprivate weak var filterView: UIView! @IBOutlet fileprivate weak var imageView: UIImageView! lazy fileprivate var transformProperty = TransformProperty() override func viewDidLoad() { super.viewDidLoad() // Bring the expanded view to the forefront if needed // self.view.bringSubview(toFront: imageView) } @IBAction private func onPinchGesture(_ sender: UIPinchGestureRecognizer) { switch sender.state { case .changed: let scale = sender.scale if scale <= 1 { break } transformProperty.scale = (sender.scale - 1.0) * 0.5 + 1.0 transform() // Darken the background color when scaled transformProperty.backgroundAlpha = (sender.scale - 1.0) * 0.8 changeBaseViewBackgroundColor() case .ended, .cancelled: revertTransform() default: break } } @IBAction private func onPanGesture(_ sender: UIPanGestureRecognizer) { guard let view = sender.view else { return } switch sender.state { case .changed: transformProperty.point = sender.translation(in: view) transform() changeBaseViewBackgroundColor() case .ended, .cancelled: revertTransform() default: break } } }
イメージビューの変形
transformPropertyの値に基づき、イメージビューを変形します。 指が離れて、操作が終わった時には、アニメーションを使って、元の位置にイメージビューを戻します。
fileprivate extension ViewController { func transform() { imageView.transform = CGAffineTransform(translationX: transformProperty.point.x, y: transformProperty.point.y) .scaledBy(x: transformProperty.scale, y: transformProperty.scale) } func revertTransform() { transformProperty = TransformProperty() UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1.0, options: .curveEaseOut, animations: { () -> Void in self.imageView.transform = CGAffineTransform.identity self.transform() self.filterView.backgroundColor = UIColor.clear }, completion: nil) } func changeBaseViewBackgroundColor() { filterView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: transformProperty.backgroundAlpha) } }
UIGestureRecognizerDelegateの実装
trueを返すことにより、一つのジェスチャー中に他のジェスチャーも受け取ることができます。
extension ViewController: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
注意点
機能を実現するためには、イメージビューを最前面に表示し、その下にフィルタービューを表示する必要があります。 Interface Builderでのビューの重なりによっては、前面にビューを持ってくる必要があります。 また、イメージビューが入れ子構造になっている場合は、親要素ごと最前面に表示する必要があります。
self.view.bringSubview(toFront: imageView) or self.view.bringSubview(toFront: scrollView) scrollView.bringSubview(toFront: stackView) stackView.bringSubview(toFront: imageView)
サンプル
Scale-ImageView@githubに動作するプロジェクトがあります。
動作確認
このTipsは、「スマホの写真素材が売買できるサイトSnapmart」を開発する中で生まれました。 実際の動作をSnapmartアプリ(iOS)から確認できますので、是非ダウンロードしてみてください!