버그 잡이

iOS/Swift 온보딩 화면 만들기 (with animation) 본문

IOS

iOS/Swift 온보딩 화면 만들기 (with animation)

버그잡이 2021. 2. 25. 19:02

 

요구 사항

1. 일반적인 온보딩 페이지

 

2. 넘길때 애니메이션 효과 적용 (이때 전환시 애니메이션 효과 적용)

    (특히, 총 4개의 온보딩 page가 있는데 마지막 화면은 button의 모양이 변경됨)

 

 

 

 

사전 조사

 

온보딩 페이지를 만드는 방법은 크게 두 가지가 있습니다.

 

1. UIPageViewController 사용

 

2. UIScrollView 사용

 

UIPageViewController는 책장을 넘기는 형태의 뷰 생성을 도와주는 객체이고

UIScollview는  isPagingEnabled 속성을 true로 하면 scroll시 page를 넘기는 것과 같은 효과를 줄 수 있습니다.

 

결론적으로 UIScrollView를 활용하는 방식을 선택했습니다

 

두 가지 이유가 있는데

첫째는 pageControl를 맨 하단이 아닌 중간에 위치시키고 싶은데 UIPageViewController에서는 그것이 불가능해 보였습니다.

둘째는 UIScrollViewDelegate의 scrollViewdidScroll() 메서드를 사용해야했습니다. 

didScroll() 메서드를 활용하면 스크롤 중 위치 값을 받을 수 있고 나는 그 값으로 애니메이션 효과를 줄 수 있다고 판단했습니다.

(UIPageViewController에도 값을 받을 수 있는 메서드들이 있지만 scrollViewdidScroll()처럼 scroll되는 모든 순간에 대해서 값을 받을 수 있는 메서드는 없는 것 같았습니다.)

 

 

 

구현 방법

 

 

1. scrollView 생성

 

isPagingEnabled = true로 설정해주고 indicator들을 없애줍니다.

저는 양끝에서 bounce 효과를 제거하기 위해서 속성을 false로 줬습니다.

private let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)).with {
    $0.isPagingEnabled = true
    $0.showsHorizontalScrollIndicator = false
    $0.showsVerticalScrollIndicator = false
    $0.bounces = false
}

 

 

 

2. page가 될 view들 추가

 

frame.origin.x = scrollWidth * CGFloat(index) 를 보고 scrollView와 그 안의 page들의 배치가 머리속에 그려지셨다면 성공입니다.

(스크린을 벗어나 page들이 옆으로 하나씩 붙는 모습을 머릿속으로 그리시면 됩니다)

private func setupPageView() {
    var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
    frame.size = scrollView.frame.size
    frame.size.height = scrollHeight

    for index in 0..<pages.count {
        frame.origin.x = scrollWidth * CGFloat(index)

        let onboardingPage = OnBoardingPageView(frame: frame)

        scrollView.addSubview(onboardingPage)
    }

    scrollView.contentSize = CGSize(width: scrollWidth * CGFloat(pages.count), height: scrollHeight)
}

 

 

 

3. 애니메이션 효과

 

원리는 간단합니다. scrollView.contentOffset / scrollView.frame.width 로 현재 페이지를 계산합니다.

여기서 point는 round()입니다. 반올림 처리로 인해서 페이와 페이지 중간 지점을 기준으로 넘어가면 올림 처리가 되고 넘어가지 않는다면 내림 처리가 됩니다.

이 pageNumber와 viewModel에 가지고 있는 currentPage 정보를 비교해서 변화가 있다면 

changePageStyleWithAnimation()이라는 메서드를 실행시켜 애니메이션 효과를 주는 것입니다. 

 

extension OnboardingViewController: UIScrollViewDelegate {

	// 당연히 viewDidLoad에서 "scrollView.delegate = self" 도 해줘야겠죠?
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let pageNumber = Int(round(scrollView.contentOffset.x / scrollView.frame.size.width))
        
        if viewModel.currentPage != pageNumber {
            self.pageControl.currentPage = pageNumber
            changePageStyleWithAnimation(prevPage: viewModel.currentPage, nextPage: pageNumber)
            
            viewModel.currentPage = pageNumber
        }
    }
    
    private func changePageStyleWithAnimation(prevPage: Int, nextPage: Int) {
        UIView.transition(with: buttonContainerView, duration: 0.3, options: [.transitionCrossDissolve], animations: {
            if nextPage == viewModel.lastPageIndex {
                self.skipButton.isHidden = true
                self.startButton.isHidden = false
                self.scrollView.backgroundColor = .white
            } else {
                self.skipButton.isHidden = false
                self.startButton.isHidden = true
                self.scrollView.backgroundColor = .gray
            }
        })
    }
}


 

chagePageStyleWithAnimation() 에서 button show/hidden 처리와 backgroundColor를 바꾸는 효과를 주었는데

여기에 추가적으로 view의 alpha값을 조정하여 view가 서서히 등장하는 효과 등을 줄 수 있습니다. 

 

 

 

4. 버튼 클릭으로 화면 넘기기

 

아래와 같은 함수를 원하는 button에 addTarget으로 연결해주면 됩니다.

setContentOffset() 함수로 원하는 page로 이동할 수 있습니다.

button 클릭으로 scroll이 움직여도 scrollViewDidScroll()이 scrollView의 움직임을 인식하기 때문에 애니메이션 효과는 동일하게 적용됩니다.

@objc func nextButtonTapped() {
    let pageNumber = pageControl.currentPage + 1
    viewModel.currentPage = pageNumber

    let x = CGFloat(pageNumber) * scrollView.frame.size.width
    scrollView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}

 

 

 

반응형
Comments