본문 바로가기

SwiftUI/Project

4. SwiftUI Launching Page 만들기

 

 

런칭 화면은 앱과 유저가 처음 접하는 뷰인 만큼, 많은 앱에서 신경을 많이 쓰는 느낌을 받는다.

이번에는 다음 기능을 목표로 런칭 뷰를 구현한다.

  • 작은 컴포넌트 들이 중앙에서 양측 사이드로 이동한다
  • Frame 역할을 하는 네모를 왼쪽에서 오른 쪽으로 점진적으로 늘어나서 만나게 한다
  • 시간을 두고 아래에 순차적으로 텍스트를 추가한다

 

1. Component 생성

 

우선 뷰를 만들고, 배경으로 쓸 이미지를 넣는다. 이미지가 필요하다면 다음 사이트를 참고하면 좋을 것 같다

2021.08.10 - [기타] - 이미지 무료로 쓸 수 있는 곳

 

이미지 무료로 쓸 수 있는 곳

무료로 고화질의 이미지를 사용할 수 있는 곳을 찾다고 좋은 곳을 발견했다. 좋은 사이트들이 있으면 추가할 예정. https://unsplash.com/ Beautiful Free Images & Pictures | Unsplash Beautiful, free images a..

devyan.tistory.com

 

 

가장 먼저, 컴포넌트들의 사이즈와 간격 등을 변수로 만들어준다. 기준은 화면의 가로 길이를 기준으로 만든다.

다음 코드를 body 위에 선언해주자.

private let mainWidth = UIScreen.main.bounds.width * 0.73
private let contentOffset: CGFloat = 18	
private let contentLine: CGFloat = 10		// content line width

private var contentWidth: CGFloat {
	(self.mainWidth - 4.0 * self.contentOffset) / 3.0
}

내부 컴포넌트의 길이(contentWidth)는 동적으로 설정되도록 해주자.

 

 

다음으로 body view 내부에 componet 들을 만들어 준다.

기본적으로 배경 사진, frame에 사용 될 네모 하나와, 내부의 동그라미, 네모, 동그라미가 필요하다.

그리고 모든 컴포넌트가 화면의 센터를 중심으로 움직이기 때문에 ZStack에 쌓아준다.

ZStack{
	
	// background
	Image("brown2")
		.resizable()
	
	// texts
	Text("Devyan")
		.font(.system(size: 35))
		.bold()
		.offset(y: (mainWidth)*0.73)
		.foregroundColor(.white)

	Text("Welcome To My Blog")
		.bold()
		.offset(y: mainWidth * 0.89)
		.foregroundColor(.white)

	// frame rectangle
	RoundedRectangle(cornerRadius: 12.0)
		.stroke(AngularGradient(gradient: Gradient(colors: [.red, .yellow, .green, .blue, .purple, .red]), center: .center), style: StrokeStyle(lineWidth: contentLine, lineCap: .round))
		.frame(width: mainWidth, height: mainWidth)
	
	// inner components
	RoundedRectangle(cornerRadius: 12)
		.stroke(Color.yellow, lineWidth: contentLine)
		.frame(width: contentWidth, height: contentWidth)
	
	Circle()
		.stroke(Color.green, lineWidth: contentLine)
		.frame(width: contentWidth, height: contentWidth)
	
	Circle()
		.stroke(Color.red, lineWidth: contentLine)
		.frame(width: contentWidth, height: contentWidth)
	
}

 

이렇게 선언하면, 내부 컴포넌트는 중첩된 상태로 중앙에 쌓여있게 된다.

공부하지 않아도 이용하기 용이하지만, Gradient와 StrokeStyle은 사용할 일이 종종 있으므로 추후 정리해 볼 예정이다.

 

 

2. Animation 설정

SwiftUI의 가장 큰 장점 중 하나가 원하는 애니메이션을 쉽게 입힐 수 있다는 것이다.

State 변수를 만들어 애니메이션이 진행되도록 한다.

 

  • 우선, 내부의 원들 중 하나는 왼쪽으로, 하나는 오른 쪽으로 이동해야한다. 이는 offset을 변경시켜 목적을 달성할 수 있다.
  • 외부 프레임의 경우는 이동이 Linear하지 않기 때문에 복잡해 보일 수 있지만 trim을 이용하면 간단하다. 아래 참고.
  • Text는 Fade-in 모션이 필요하다. 텍스트는 Main Title, Subtitle에 따로 애니메이션을 추가한다.

만약, 한 번에 모든 애니메이션을 실행하려면 하나의 변수만 선언해도 문제가 없지만, 시간을 두고 하나씩 변화시킬 것이기 때문에 각각의 변환에 다른 변수를 선언해준다. 다시 body 위에 변수들을 선언해주자.

@State private var isCentered: Bool = true			// is all components centered ?
@State private var isMaintitled: Bool = false
@State private var isSubtitled: Bool = false
@State private var isRoundGradient: Bool = false

 

이제 modifiers를 추가해 주자.

 

...

// title
Text("Devyan")
	...
	.colorMultiply(isMaintitled ? .white : .clear)

Text("Welcome To My Blog")
	...
	.colorMultiply(isSubtitled ? .white : .clear)

// frame rectangle
RoundedRectangle(cornerRadius: 12.0)
	.trim(from: isRoundGradient ? 0 : 1, to: isRoundGradient ? 1 : 0)
	...

// inner components
RoundedRectangle(cornerRadius: 12)
	// no change

Circle()
	...
	.offset(x: isCentered ? 0 : -(contentOffset + contentWidth))

Circle()
	...
	.offset(x: isCentered ? 0 : (contentOffset + contentWidth))

주의할 점은 Text의 foreground는 animation 적용 대상이 아니다. 추측컨대, 뷰 객체를 받기 때문이 아닐까 싶다.

 

하지만 colorMultiply는 애니메이션이 가능하기 때문에 이를 이용해주도록 한다. isMainTitled는 default로 false로 설정되어 있기 때문에 white x white 지만, true로 변환되는 순간 clear(transparent) 와 섞이며 투명색이 된다.

 

또한 frame rectangle의 경우는 (from: 0, to:1) 이 처음부터 끝까지 이고, from, to를 조정하여 일부를 잘라낼 수 있다.

단순히 .trim(from: 0, to: isRoundGradient ? 1 : 0) 이렇게 선언하면 시계방향으로 그려지지만, 우리가 원하는 애니메이션은 아니다.

좌에서 우로 그려지는 애니메이션을 위해 from, to 모두 조건문을 추가해주자.

 

마지막으로, 원의 오프셋은 예쁘게 보이기 위해 컴포넌트의 길이에다가 오프셋을 더해 좌, 우로 이동시켜준다.

 

 

3. 애니메이션 설정

이제 실시간으로 State 변수를 trigger 시켜주는 일만 남았다.

뷰가 나타날 때 변수를 바꾸는 과정을 추가하되, 시간을 두고 각각을 변환하기 위해 DispatchQueue를 이용하자.

 

 

// Gradient 종류 및 사용법 추가 예정