Swiftで作るかっこいいwalkthrough(Airbnb風)
Airbnbのwalkthroughがかっこいいなーとおもったので同じようなものをつくってみました。
Airbnbの良いところは、
- 横にスクロールすると今の画像がフェードアウトし、次の画像がフェードインする
- 画像が常にアニメーションしている
- 横スクロールに合わせて下のボタンの色がなめらかに変わる
です。
今回はこれらの項目を実装しました。
1つめ | 横にスクロールすると今の画像がフェードアウトし、次の画像がフェードインする
今回はImageViewを乗せるためのimageViewContainerを用意し、そちらにImageViewを乗せるという方法で実装しました。
walkthroughの長さによってImageViewの数が変わってくるので、常に全てを表示するのではなく、今表示しているものと左右にスクロールしたときに出てくるものの計3枚のImageViewが一度にこのimageViewContainerにのっています。 左右にスクロールした時に出てくるビューはスクロールの開始時に追加され、終了時に取り除かれるので、スクロールしていないときはそのとき表示されているImageViewが表示されるようにしました。
ViewDidLoad
まずViewDidLoad内で下準備をします。
今回scrollViewはstoryBoardで実装し、imageViewContainerはコードで実装するという変なことをしたので、余計な部分がありますが気にしないでください。
基本的には、ImageViewを用意し配列に保存し、一枚目のImageViewだけimageViewContainerに追加し表示する処理をおこなっています。
class ViewController: UIViewController, UIScrollViewDelegate { var imageViewContainer: UIView! var imageViewArray = [UIImageView]() override func viewDidLoad() { super.viewDidLoad() /** 関係ない部分は省略 */ // imageViewContainer. scrollViewの後ろ側に追加 imageViewContainer = UIView(frame: CGRectMake(0, 0, view.frame.width, view.frame.width)) view.insertSubview(imageViewContainer, belowSubview: scrollView) // ImageViewの設定 let frame = CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height) var imageView1 = UIImageView(image: UIImage(named: "image1.jpg")) imageView1.frame = frame imageViewArray.append(imageView1) var imageView2 = UIImageView(image: UIImage(named: "image2.jpg")) imageView2.frame = frame imageViewArray.append(imageView2) var imageView3 = UIImageView(image: UIImage(named: "image3.jpg")) imageView3.frame = frame imageViewArray.append(imageView3) // 最初は1枚目だけを追加. 後のビューはスクロールに合わせて追加・削除する. imageViewContainer.addSubview(imageViewArray[0]) }
UIScrollViewDelegate
アニメーションはscrollViewのdelegateメソッドを利用します。
まず初めにscrollViewWillBeginDraggingにて、左右のImageViewをimageViewContainerに追加します。
次にscrollViewDidScrollにて、ImageViewのalpha値を変更しフェードイン・アウトを実装します。
alpha値は現在のcontentOffsetと今のページのx座標の差分からスクロール量を計算し、それを元に計算します。progressがスクロールの進み具合を示す変数で、0~1の値をとるようになっているので、この変数を使ってalpha値を決めていきます。
func scrollViewWillBeginDragging(scrollView: UIScrollView) { addBothSidesImageView() } func scrollViewDidScroll(scrollView: UIScrollView) { let pageWidth = scrollView.frame.size.width if Int(fmod(scrollView.contentOffset.x , pageWidth)) == 0 { pageControl.currentPage = Int(scrollView.contentOffset.x / pageWidth) imageViewArray[pageControl.currentPage].alpha = 1.0 removeBothSidesImageView() } else { // ここで画像と色の変化を実装 let currentView = imageViewArray[pageControl.currentPage] if let index = nextIndex { let def = pageWidth * CGFloat(pageControl.currentPage) - scrollView.contentOffset.x let progress = CGFloat(abs(def)) / pageWidth let nextView = imageViewArray[index] currentView.alpha = 1 - progress nextView.alpha = progress /** 関係ない部分は省略 */ } } } private var nextIndex: Int? { get { var nextIndex: Int let currentPageX = CGFloat(pageControl.currentPage) * view.frame.width if scrollView.contentOffset.x < currentPageX { // 右スワイプ nextIndex = pageControl.currentPage - 1 } else { nextIndex = pageControl.currentPage + 1 } if nextIndex < 0 || pageCount <= nextIndex { return nil } return nextIndex } }
2つめ | 画像が常にアニメーションしている
今回はimageViewContainerを作っているので、こちらを常にアニメーションすれば、ImageViewが常に変化するようになります。
アニメーションを永遠に繰り返すためにrepeatCountをHUGEにすることと、拡大縮小を繰り返すためにautoreversesをtrueにすることでこのようなアニメーションを実現できます。
override func viewDidLoad() { var animation = CABasicAnimation(keyPath: "transform.scale") animation.duration = 10.0 animation.repeatCount = HUGE animation.autoreverses = true animation.fromValue = 1.0 animation.toValue = 1.1 imageViewContainer.layer.addAnimation(animation, forKey: "scale-layer") }
3つめ | 横スクロールに合わせて下のボタンの色がなめらかに変わる
基本的には1つめの画像のフェードインと同じです。色の情報をarrayに格納しておき、その情報を元になめらかに色を変化させていきます。
画像はalpha値を変更したことに対し、今回は色のRGB値を変更することでなめらかに色を変化させていきます。
UIColorからRGB値を取得するためにはgetRed(red:UnsafeMutablePointer
これで今の色と次の色のRGB値を算出し、その差分をスクロールの量に合わせて追加することでなめらかに色を変化させることができます。
func scrollViewDidScroll(scrollView: UIScrollView) { let pageWidth = scrollView.frame.size.width if Int(fmod(scrollView.contentOffset.x , pageWidth)) == 0 { /** 関係ないところは省略 */ } else { // ここで画像と色の変化を実装 let currentView = imageViewArray[pageControl.currentPage] if let index = nextIndex { let def = pageWidth * CGFloat(pageControl.currentPage) - scrollView.contentOffset.x let progress = CGFloat(abs(def)) / pageWidth /** ImageViewの変更 - 略 - */ let nextColor = colorArray[index] var currentColor = colorArray[pageControl.currentPage] var currentRGBColor = getRGB(currentColor) var defColors = defTwoColors(currentColor, second: nextColor) footerView.backgroundColor = UIColor( red: currentRGBColor.red + defColors.red * progress, green: currentRGBColor.green + defColors.green * progress, blue: currentRGBColor.blue + defColors.blue * progress, alpha: currentRGBColor.alpha + defColors.alpha * progress) } } } private func getRGB(color: UIColor) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { var rgbValue = (red: CGFloat(0.0), green: CGFloat(0.0), blue: CGFloat(0.0), alpha: CGFloat(0.0)) color.getRed(&rgbValue.red, green: &rgbValue.green, blue: &rgbValue.blue, alpha: &rgbValue.alpha) return (rgbValue.red, rgbValue.green, rgbValue.blue, rgbValue.alpha) } private func defTwoColors(first: UIColor, second: UIColor) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { var currentColor = (red: CGFloat(0.0), green: CGFloat(0.0), blue: CGFloat(0.0), alpha: CGFloat(0.0)) var nextColor = (red: CGFloat(0.0), green: CGFloat(0.0), blue: CGFloat(0.0), alpha: CGFloat(0.0)) first.getRed(¤tColor.red, green: ¤tColor.green, blue: ¤tColor.blue, alpha: ¤tColor.alpha) second.getRed(&nextColor.red, green: &nextColor.green, blue: &nextColor.blue, alpha: &nextColor.alpha) return (nextColor.red - currentColor.red, nextColor.green - currentColor.green, nextColor.blue - currentColor.blue, nextColor.alpha - currentColor.alpha) }
最後に
全体像は以下のようになってます。