SwiftUI - Learn by Doing
Preserve Vector Data: 백터 이미지를 저장해서 다양한 크기로 사용할때 효과적이지만, 앱의 용량이 커진다는 단점이 있다.
- .pdf, .svg 형식의 이미지가 백터 형식 이미지다. (iOS 13 이상부터 가능)
그러면 백터(Vector)형식의 이미지는 무엇인가?
- 백터 이미지란: 점과 점을 연결해 수학적 원리로 그림을 그려 표현하는 방식이다.
- 이미지에 대한 정보가 모양과 선에 대한 형태로 파일에 저장되어 있기 때문에 확대를 해도 깨지지 않는다.
다음으로 Launch Screen을 만들 것이다.
위에처럼 SwiftUI 에서는 Launch Screen을 만드는 방법은 2가지가 있다.
1. info -> Launch Screen 을 통한 방법
2. 아래처럼 파일을 생성하여 꾸며주는 방법
위에 사진에서 설정한 부분들
* Image View와 Label을 화면 가운데에 정렬시켰다.
* 배경 색깔을 전체화면으로 설정하였다. safe area가 아니라 View로 설정해야한다.
다음은 아래 사진처럼 View를 만드는 것이다.
import SwiftUI
struct CardView: View {
// MARK: - PROPERTIES
var gradient: [Color] = [Color("Color01"), Color("Color02")]
// MARK: - CARD
var body: some View {
ZStack {
Image("developer-no1")
VStack {
Text("SwiftUI")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
Text("Better apps. Less code.")
.fontWeight(.light)
.foregroundColor(Color.white)
.italic()
}
.offset(y: -218)
Button {
print("Button was tapped.")
} label: {
HStack {
Text("Learn".uppercased())
.fontWeight(.heavy)
.foregroundColor(Color.white)
.accentColor(Color.white)
Image(systemName: "arrow.right.circle")
.font(Font.title.weight(.medium))
.accentColor(Color.white)
}
.padding(.vertical)
.padding(.horizontal, 24)
.background(LinearGradient(gradient: Gradient(colors: gradient), startPoint: .leading, endPoint: .trailing))
.clipShape(Capsule())
.shadow(color: Color("ColorShadow"), radius: 6, x: 0, y: 3)
}
.offset(y: 210)
}
.frame(width: 335, height: 545)
.background(LinearGradient(gradient: Gradient(colors: gradient), startPoint: .top, endPoint: .bottom))
.cornerRadius(16)
.shadow(radius: 8)
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView()
}
}
새로 알게된 것들
italic() - 텍스트 기울기 설정
.offset -
y: 값이 증가할수록 하단에 배치, 값이 감소할수록 상단에 배치, 0은 화면 중간이다.
x: 값이 증가할수록 우측에 배치, 값이 감소할수록 좌측에 배치, 0은 화면 중간이다.
UI에 대해서 직접 화면에서 속성값을 설정할 수 있다는 것을 알게 됐다.
다음으로는 좌우로 스크롤 가능한 ContentView 만들기
import SwiftUI
struct ContentView: View {
// MARK: - CONTENT
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0 ..< 6) { item in
CardView()
}
}
}
}
}
// MARK: - PREVIEW
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 11 Pro")
.previewLayout(.sizeThatFits)
}
}
이제 유지보수가 편하도록 코드들을 추가 수정해준다.
import SwiftUI
// MARK: - CARD MODEL
struct Card: Identifiable {
var id = UUID() // 카드에 고유 id를 부여함으로써 카드를 구분하는데 사용한다
var title: String
var headline: String
var imageName: String
var callToAction: String
var message: String
var gradientColors: [Color]
}
import SwiftUI
// MARK: - CARD DATA
let cardData: [Card] = [
Card(
title: "SwiftUI 2",
headline: "Better apps. Less code.",
imageName: "developer-no1",
callToAction: "Design",
message: "Introducing a modern way to design user interfaces for any Apple devices.",
gradientColors: [Color("Color01"), Color("Color02")]),
Card(
title: "iOS 14",
headline: "Dramatic new look.",
imageName: "developer-no2",
callToAction: "Discover",
message: "Get ready for iOS 14 and learn about the new Dark Mode.",
gradientColors: [Color("Color03"), Color("Color04")]
),
Card(
title: "Swift 5",
headline: "Everyone can code.",
imageName: "developer-no3",
callToAction: "Implement",
message: "A modern yet powerful programming language that is also easy to learn.",
gradientColors: [Color("Color05"), Color("Color06")]
),
Card(
title: "Catalyst",
headline: "Bring Your iPad App to Mac.",
imageName: "developer-no4",
callToAction: "Distribute",
message: "Now it’s incredibly simple to start building a native Mac app from your iPad app.",
gradientColors: [Color("Color07"), Color("Color08")]
),
Card(
title: "Playgrounds",
headline: "Engaging way to learn coding.",
imageName: "developer-no5",
callToAction: "Experiment",
message: "A new way to create code. On the best device for learning.",
gradientColors: [Color("Color09"), Color("Color10")]
),
Card(
title: "Xcode 12",
headline: "The best tool you need to build apps.",
imageName: "developer-no6",
callToAction: "Develop",
message: "Bring your ideas to life and build awesome apps for all platforms.",
gradientColors: [Color("Color11"), Color("Color12")]
)]
import SwiftUI
struct CardView: View {
// MARK: - PROPERTIES
var card: Card
var gradient: [Color] = [Color("Color01"), Color("Color02")]
// MARK: - CARD
var body: some View {
ZStack {
Image(card.imageName)
VStack {
Text(card.title)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
Text(card.headline)
.fontWeight(.light)
.foregroundColor(Color.white)
.italic()
}
.offset(y: -218)
Button {
print("Button was tapped.")
} label: {
HStack {
Text(card.callToAction.uppercased())
.fontWeight(.heavy)
.foregroundColor(Color.white)
.accentColor(Color.white)
Image(systemName: "arrow.right.circle")
.font(Font.title.weight(.medium))
.accentColor(Color.white)
}
.padding(.vertical)
.padding(.horizontal, 24)
.background(LinearGradient(gradient: Gradient(colors: card.gradientColors), startPoint: .leading, endPoint: .trailing))
.clipShape(Capsule())
.shadow(color: Color("ColorShadow"), radius: 6, x: 0, y: 3)
}
.offset(y: 210)
}
.frame(width: 335, height: 545)
.background(LinearGradient(gradient: Gradient(colors: card.gradientColors), startPoint: .top, endPoint: .bottom))
.cornerRadius(16)
.shadow(radius: 8)
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView(card: cardData[1])
}
}
import SwiftUI
struct ContentView: View {
// MARK: - PROPERTIES
var cards: [Card] = cardData
// MARK: - CONTENT
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 20) {
ForEach(0 ..< 6) { item in
// CardView()
CardView(card: cardData[item])
}
}
}
}
}
// MARK: - PREVIEW
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 11 Pro")
.previewLayout(.sizeThatFits)
}
}
다음으로는 AVFoundation 프레임워크를 사용하여 소리 파일을 실행시키는 것이다.
import Foundation
import AVFoundation
// MARK: - AUDIO PLAYER
var audioPlayer: AVAudioPlayer?
func playSound(sound: String, type: String) {
if let path = Bundle.main.path(forResource: sound, ofType: type) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL(filePath: path))
audioPlayer?.play()
} catch {
print("오디오 재생 실패")
}
}
}
아래 영상처럼 애니메이션 넣기
import SwiftUI
struct CardView: View {
// MARK: - PROPERTIES
var card: Card
@State private var fadeIn: Bool = false
@State private var moveDownward: Bool = false
@State private var moveUpward: Bool = false
// MARK: - CARD
var body: some View {
ZStack {
Image(card.imageName)
.opacity(fadeIn ? 1.0 : 0.0)
VStack {
Text(card.title)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
Text(card.headline)
.fontWeight(.light)
.foregroundColor(Color.white)
.italic()
}
.offset(y: moveDownward ? -218 : -300)
Button {
print("Button was tapped.")
playSound(sound: "sound-chime", type: "mp3")
} label: {
HStack {
Text(card.callToAction.uppercased())
.fontWeight(.heavy)
.foregroundColor(Color.white)
.accentColor(Color.white)
Image(systemName: "arrow.right.circle")
.font(Font.title.weight(.medium))
.accentColor(Color.white)
}
.padding(.vertical)
.padding(.horizontal, 24)
.background(LinearGradient(gradient: Gradient(colors: card.gradientColors), startPoint: .leading, endPoint: .trailing))
.clipShape(Capsule())
.shadow(color: Color("ColorShadow"), radius: 6, x: 0, y: 3)
}
.offset(y: moveUpward ? 210 : 300)
}
.frame(width: 335, height: 545)
.background(LinearGradient(gradient: Gradient(colors: card.gradientColors), startPoint: .top, endPoint: .bottom))
.cornerRadius(16)
.shadow(radius: 8)
.onAppear {
withAnimation(.linear(duration: 1.2)) {
self.fadeIn.toggle()
}
withAnimation(.linear(duration: 0.8)) {
self.moveDownward.toggle()
self.moveUpward.toggle()
}
}
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView(card: cardData[1])
}
}
.opacity(fadeIn ? 1.0 : 0.0) : withAnimation(.linear(duration: 1.2))를 사용하여 투명도를 100%에서 0%로 1.2초동안 바꿔준다.
.offset(y: moveDownward ? -218 : -300) : withAnimation(.linear(duration: 0.8))를 사용하여 위에서 밑으로 내려오도록 설정
- 참고로 -300이라는 좌표를 사용하는 이유는 view가 안보이는 시점부터 시작한다는 의미다.
.offset(y: moveUpward ? 210 : 300) : withAnimation(.linear(duration: 0.8))를 사용하여 밑에서 위로 올라오도록 설정
다음으로는 햅틱 (진동 피드백을 설정하는 방법이다)
- soft, light, medium, rigid, [custom], heavy 종류가 있다.
var hapticImpact = UIImpactFeedbackGenerator(style: .heavy)
hapticImpact.impactOccurred()
다음으로는 버튼을 눌렀을때 알림창이 뜨는 것을 만드는 것
@State private var showAlert: Bool = false
alert(isPresented: $showAlert) {
Alert(title: Text(card.title), message: Text(card.message), dismissButton: .default(Text("OK")))
}
총 코드
import SwiftUI
struct CardView: View {
// MARK: - PROPERTIES
var card: Card
@State private var fadeIn: Bool = false
@State private var moveDownward: Bool = false
@State private var moveUpward: Bool = false
@State private var showAlert: Bool = false
var hapticImpact = UIImpactFeedbackGenerator(style: .heavy)
// MARK: - CARD
var body: some View {
ZStack {
Image(card.imageName)
.opacity(fadeIn ? 1.0 : 0.0)
VStack {
Text(card.title)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
Text(card.headline)
.fontWeight(.light)
.foregroundColor(Color.white)
.italic()
}
.offset(y: moveDownward ? -218 : -300)
Button {
print("Button was tapped.")
playSound(sound: "sound-chime", type: "mp3")
hapticImpact.impactOccurred()
self.showAlert.toggle()
} label: {
HStack {
Text(card.callToAction.uppercased())
.fontWeight(.heavy)
.foregroundColor(Color.white)
.accentColor(Color.white)
Image(systemName: "arrow.right.circle")
.font(Font.title.weight(.medium))
.accentColor(Color.white)
}
.padding(.vertical)
.padding(.horizontal, 24)
.background(LinearGradient(gradient: Gradient(colors: card.gradientColors), startPoint: .leading, endPoint: .trailing))
.clipShape(Capsule())
.shadow(color: Color("ColorShadow"), radius: 6, x: 0, y: 3)
}
.offset(y: moveUpward ? 210 : 300)
}
.frame(width: 335, height: 545)
.background(LinearGradient(gradient: Gradient(colors: card.gradientColors), startPoint: .top, endPoint: .bottom))
.cornerRadius(16)
.shadow(radius: 8)
.onAppear {
withAnimation(.linear(duration: 1.2)) {
self.fadeIn.toggle()
}
withAnimation(.linear(duration: 0.8)) {
self.moveDownward.toggle()
self.moveUpward.toggle()
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text(card.title), message: Text(card.message), dismissButton: .default(Text("OK")))
}
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView(card: cardData[1])
}
}