[Quick Kick] / 프로젝트 5일차

post-thumbnail

-Today's Learning Content-

  • 온보딩 페이지 구현

1. 온보딩 페이지 구현

내용 정리

앱을 최초 실행한 유저들에게만 보여줄 온보딩 페이지를 구현하였다.
원래는 UIPageViewController를 사용해서 구현하려고 했으나, 뷰와 뷰 컨트롤러를 재사용하기 위해 UIViewController를 하나만 사용하여 온보딩 페이지를 구현해 보았다.

1) 오늘 구현한 뷰

런치온보딩1온보딩2온보딩3
LunchOnboading-1Onboading-2Onboading-3

2) 스와이프 제스처 사용하기

페이지네이션 기능을 사용하지 않고 옆으로 넘기듯이 화면을 바꾸려면 어떤 기능을 사용할 수 있을까 고민하다가 UISwipeGesture를 떠올렸고, 지금 상황에 적용해볼 수 있을 것 같아서 바로 사용하기로 결정했다.

/// 뷰에 스와이프 제스처를 추가하는 메소드
func addSwipeGesture() {
	let swipeGestureRight = UISwipeGestureRecognizer(target: self, action: #selector(pageSwipe))
	swipeGestureRight.direction = .right
        
	let swipeGestureLeft = UISwipeGestureRecognizer(target: self, action: #selector(pageSwipe))
	swipeGestureLeft.direction = .left
        
	self.addGestureRecognizer(swipeGestureRight)
	self.addGestureRecognizer(swipeGestureLeft)
}

스와이프 제스처를 사용할 때 주의할 점이 있는데, 스와이프 제스처는 방향에 따라 각각 하나의 제스처를 정의해 주어야 한다는 점이다.
위의 코드에서도 .right, .left로 스와이프 제스처의 direction을 설정해 주고 각각 하나의 액션을 정의하여 사용했다.

처음에는 이걸 몰라서 direction설정도 안 한 채 사용했다가 .right 스와이프 제스처만 작동하는 버그가 있어서 뭐가 문제인지 엄청 고민했는데... 제대로 공부를 하고 사용했다면 이런 일이 없었을텐데 아쉽다.

어쨌든 위의 제스처 액션이 실행되면 작동하는 메소드는 아래의 코드이다.

/// 뷰의 화면을 업데이트 하는 메소드
/// - Parameter gesture: 스와이프 방향
func pageSwipe(_ gesture: UIGestureRecognizer) {
	if let swipeGesture = gesture as? UISwipeGestureRecognizer {
		switch swipeGesture.direction {
		case UISwipeGestureRecognizer.Direction.right:
			self.onboardingDelegate?.changedView(.right)
                
		case UISwipeGestureRecognizer.Direction.left:
			self.onboardingDelegate?.changedView(.left)
                
		default: break
		}
	}
}

이번 구현에서는 동작이 간단하기 때문에 방향 별로 하나의 메소드를 새로 만들기보다 메소드 내부에서 분기를 나눠주기로 했다.
파라미터로 direction을 받고, 그 방향에 따라 다른 동작이 실행되도록 하는 것이다.

처음에는 분기를 나눠서 다른 동작을 만들려고 switch문을 썼는데... 델리게이트 패턴으로 파라미터에 direction 값을 직접 입력하다보니 그럴 필요가 없어져서 아래 코드처럼 짧게 써도 된다는 걸 알았다...

func pageSwipe(_ gesture: UIGestureRecognizer) {
	if let swipeGesture = gesture as? UISwipeGestureRecognizer {
		self.onboardingDelegate?.changedView(swipeGesture.direction)
	}
}

처음 써보는 기능이라 조금 헤맸지만 어쨌든 완성했다.

3) 뷰 모델 만들기

이번에는 뷰를 재활용 하기 위해 뷰에 필요한 데이터를 정의하는 모델을 만들었다.
나중에 뷰 컨트롤러에서는 이 데이터모델의 배열을 가지고 화면을 전환하는 듯한 연출을 보여주게 된다.

온보딩 뷰에서 값이 변하는 것은 title, infoLabel, image, buttonHidden으로 4가지 값이다.
이를 모두 정의하는 데이터 모델을 만들어주자.

// 온보딩 뷰 데이터 모델
struct OnboardingDataModel {
    let title: String
    let info: String
    let image: UIImage?
    let isButtonHidden: Bool
}

그리고 뷰 컨트롤러에서 데이터 모델의 배열을 생성해준다.

/// 온보딩화면의 데이터 모델 배열
private let models: [OnboardingDataModel] = {
        
	let firstOnboardingView = OnboardingDataModel(title: "환영합니다!!",
                                                      info: "퀵킥은 목적지까지\n빠르게 이동하기 위해\n공용 킥보드를\nQuick 하게\n빌릴 수 있는 앱입니다!",
                                                      image: UIImage(named: "onboadingImage_kickboard"),
                                                      isButtonHidden: true)
        
	let secondOnboardingView = OnboardingDataModel(title: "잠깐!!",
                                                       info: "안전모와 면허는\n필수인거 아시죠??",
                                                       image: UIImage(named: "onboadingImage_safe"),
                                                       isButtonHidden: true)
        
	let thirdOnboardingView = OnboardingDataModel(title: "시작합시다!!",
                                                      info: "준비 되셨나요?\n지금 바로\nQuick하게\nKick보드를\n타러가요!!",
                                                      image: nil,
                                                      isButtonHidden: false)
        
	return [firstOnboardingView, secondOnboardingView, thirdOnboardingView]
}()

4) 페이지네이션? 구현하기

이제 페이지네이션?을 구현할 차례이다. 사실 페이지네이션이라고는 할 수 없는게, 마치 그렇게 보이는 것처럼 트릭으로 보여주는 것이지 실제로는 뷰 화면이 바뀔 뿐이다.

우선 현재 페이지 인덱스를 알 수 있도록 새로운 프로퍼티를 정의해 준다.

private var currentPageIndex: Int = 0 // 첫 페이지를 먼저 보여줌

그리고 현재 페이지 인덱스에 따라 뷰가 바뀌도록 하기 위해 새로운 메소드를 만들어준다.

/// 현재 뷰의 표시 데이터를 변경하는 메소드
/// - Parameter data: 데이터 모델
func changedData(_ data: OnboardingDataModel) {
	self.titleLabel.attributedText = infoLabelConfig(data.title)
	self.infoLabel.attributedText = infoLabelConfig(data.info)
	self.infoImageView.image = data.image
	self.startButton.isHidden = data.isButtonHidden
	self.layoutIfNeeded()
}

위의 메소드는 뷰 내부에서 구현한 메소드로, 파라미터로 뷰의 데이터모델 타입을 받아서 뷰 요소의 값들을 수정하는 메소드이다. 스와이프를 통해 스와이프 메소드를 실행하면 이 메소드가 실행되면서 자연스럽게 화면의 구성이 바뀌도록 의도했다.

/// 온보딩 화면을 바꿔주는 메소드
/// - Parameter direction: 스와이프 방향
func changedView(_ direction: UISwipeGestureRecognizer.Direction) {
	switch direction {
	case .right:
		if self.currentIndex > 0 {
			self.currentIndex -= 1
			swipePage()
		}
            
	case .left:
		if self.currentIndex < self.models.count - 1 {
			self.currentIndex += 1
			swipePage()
		}
            
	default: break
	}
}

위의 메소드는 스와이프를 했을 때 실행되는 메소드로, 현재 페이지 인덱스 값을 스와이프 방향에 따라 + 하거나 - 하도록 한다. 그리고 swipePage()라는 메소드를 호출하여 자연스러운 화면 전환을 구현하였다.

4) 구현 결과물


-Today's Lesson Review-

오늘은 프로젝트의 온보딩 화면을 구현했다.
처음에는 UIPageViewController를 사용하려고 했으나, 뷰의 UI 구성이 흡사해서
재사용할 수 있을 것 같은데 꼭 UIViewController3개나 만들 필요가 있을까?
라는 생각이 들어서 1개의 UIViewController1개의 UIView로만 만들어 봤는데,
생각보다 잘 동작되서 나도 놀랐다.
이번 경험을 통해 앞으로도 뷰의 재활용성을 생각하며 작업을 해야겠다고 생각했다.
profile
이유있는 코드를 쓰자!!

2개의 댓글

comment-user-thumbnail
2024년 12월 22일

캬 올게 왔구나 갓상경의 온보딩 페이지
재사용, 스와이프 제스쳐, 페이징 모든 게 멋지지만 저는 저 텍스트와 이미지가 위에서 아래로, 아래에서 위로 쓱 쓱 밀리면서 나타나는 애니메이션까지 정말 인상 깊었습니다!

1개의 답글