SwiftUI View 1 (Text, Image, Layout)
Updated:
🔷 UIkit vs SwiftUI
- UIkit 을 대응으로 구송요소들과 SwiftUI 요소 들과 SwiftUI 의 요소들을 한눈에 비교하는 표 입니다
👉 Views and Controls
UIKit | SwiftUI |
---|---|
UILabel | Text |
UITextField | TextField |
UITextField(isSecure) | SecureField |
UIButton | Button |
UIImageView | Image |
UISwitch | Toggle |
UISlider | Slider |
UIStepper | Stepper |
UIPickerView | Picker(WheelPickerStyle) |
UISegmentedControl | Picker(SegmentedPickerStyle) |
UIDatePicker | DatePicker |
UITextView | 동일 요소 없음 |
UIStackView | HStack(가로), VStack(세로) |
UIScrollView | ScrollView |
UITableView(Plain) | List |
UITableView(Grouped) | List(GroupedListStyle) |
UICollectionView | 동일 요서 없음 |
👉 ViewController
UIKit | SwiftUI |
---|---|
UIViewController | View |
UINavigationController | NavigationView |
UITabBarController | TabView |
UISpritController | NavigationView |
UITableViewController | List |
UICollectionViewController | 동일요소 없음 |
UIAlertController(actionSheet 스타일) | ActionSheet |
UIAlertController(alert 스타일) | Alert |
🔷 Text
Text
는 UIKit 의 UILabel 의 역활을 하는데 SwiftUI 에서는 Button, Picker, Toggle 에서도 Text
를 사용하기 때문에 가장 기본적이면서도 많이 쓰이는 뷰가 됩니다
// 텍스트에 제공되는 여러 가지 수식어를 이용해 쉽게 문자열을 조작할 수 있습니다
struct Example01: View {
var body: some View {
VStack(spacing: 30) { // 세로방향 뷰를 배열하는 stackView
Text("폰트와 굵기 설정")
.font(.title) // 폰트 설정
.fontWeight(.black) // 폰트 굵기
Text("글자색은 foreground, 배경은 background")
.foregroundColor(.white) // 글자 색
.padding() // 텍스트 주변 여백 설정
.background(Color.blue) // 텍스트의 배경 설정(Color 명시)
Text("커스텀 폰트, 볼드체, 이탤릭체, 밑줄, 취소선")
.font(.custom("Menlo", size: 16)) // 커스텀 폰트 설정
.bold() // 볼드체
.italic() // 이탤릭체
.underline() // 밑줄
.strikethrough() // 취소선
Text("라인 수 제한과 \n 텍스트 정렬 기능입니다. \n 이건 안보여야 됩니다 2줄까지만..")
.lineLimit(2) // 텍스트를 최대 2줄 까지만 표현
.multilineTextAlignment(.center) // 다중행 문자열의 정렬 방식 지정
.fixedSize() // 주어진 공간의 크기가 작아도 텍스트를 생략하지 않고 표현되도록 설정
// 2개 이상의 텍스트를 하나로 묶어서 동시에 적용할 수 있습니다
(Text("자간과 기준선").kerning(8) // 자간
+ Text("조정도 쉽게 가능합니다").baselineOffset(8)) // 기준선
.font(.system(size: 19))
}
}
}
🔶 수식어 적용 순서 유의 사항
-
뷰에 수식어를 적용할 때는 유의해야 할 점들이 있습니다. 각각의 뷰는 그 자체가 가진 수식어와 뷰 프로토콜이 가진 수식어로 나뉩니다.
-
이름은 동일하지만 텍스트에 정의된 수식어는 반환 타입이 Text 이고, 뷰 프로토콜에 정의된 수식어는 반환 타입니 some View 와 같이 서로 다른 것을 볼 수 있습니다
Text("SwiftUI")
.font(.title) // Text - 호출자의 타입이 Text 이므로 다시 반환
.bold() // Text
.padding() // View - padding 수식어 호출 이후에는 Text 가 아닌 View 반환
Text("SwiftUI")
.bold() // Text
.padding() // View - padding 수식어 호출 이후에는 Text 가 아닌 View 반환
.font(.title) // View - 동일한 font 수식어를 호출해도 호출자에 따라 반환 타입이 다릅니다
Text("SwiftUI")
.padding() // View
.bold() // 컴파일 오류 : 뷰 프로토콜에는 bold 수식어가 없으므로 오류가 발생합니다
.font(.title)
Text("SwiftUI")
.padding() // View
.font(.title) // View
.bold() // 컴파일 오류: 뷰 프로토콜에는 bold 수식어가 없습니다
- 수식어는 이전의 뷰를 감싼 새로운 뷰를 만들어 내고, 그다음 수식어는 다시 그 뷰를 감쌉니다. 그래서 수식어를 적용하는 순서에 따라 결과가 많이 달라 집니다
// 수식어 적용 순서에 따라 실행 결과가 달라질 수 있습니다
struct Example02: View {
var body: some View {
VStack(spacing: 20) {
Text("😀 🥰 🤗 😶🌫️")
.font(.largeTitle)
.background(Color.blue) // background 우선 적용
.padding()
Text("😀 🥰 🤗 😶🌫️")
.font(.largeTitle)
.padding() // padding 우선 적용
.background(Color.blue)
}
}
}
🔷 Image
-
Image 는 UIKit 의 UIImageView 와 같이 저장된 이미지를 표현하는 뷰이지만, 사용법은 SwiftUI 에서 간편해 졌습니다.
-
SwiftUI 에서 이미지를 다룰때는 이미지는 기본적으로 주어진 공간과 관계없이 그 고유의 크기를 유지합니다. 그래서 이지지 크기는 변하지 않고 뷰가 차지 하는 공간만 달라져서 아래와 같이 겹쳐서 나타 나게 됩니다
// 프레임 수식어만으로는 이미지의 크기가 변화하지 않습니다.
struct Example01: View {
var body: some View {
HStack {
Image("SwiftUI") // 원본 100 X 100 pt
Image("SwiftUI").frame(width: 50, height: 50) // frame 만으로 사이즈가 달라지지 않습니다
Image("SwiftUI").frame(width: 200, height: 200) // 사이즈 변동 없음
}
}
}
👉 Resizable
-
이미지의 크기르 ㄹ변경하는 경우 resizable 수식어를 적용해야 합니다. resizable 은 이미지에서만 사용 가능한 수식어 인데 frame 수식어보다 먼저 적용이 되야 합니다
-
resizable 수식어는 이미지 타입에 선언되어 있는 것으로 뷰 프로토콜이 반환되는 수식어를 사용하기 전에 호출해야 한다는 것입니다. frame 과 resizable 순서가 바뀌면 오류가 방생됩니다
// resizable 수식어는 부모 뷰가 제공하는 공간에 맞게 이미지 크기를 조정합니다.
// 비율과 크기는 콘텐트 모드에 따라 달라질 수 있습니다
struct Example02: View {
var body: some View {
VStack(spacing: 80) {
HStack {
Image("SwiftUI")
Image("SwiftUI").resizable().frame(width: 50, height: 50)
Image("SwiftUI").resizable().frame(width: 200, height: 200)
}
HStack{
// capInset 매개 변수에 늘어날 영역 지정 .resizingMode 생략 시 stretch 적용
Image("SwiftUI").resizable(capInsets: .init(top: 0, leading: 50, bottom: 0, trailing: 0)).frame(width: 150, height: 150)
Image("SwiftUI").resizable(resizingMode: .tile).frame(width: 150, height: 150)
}
}
}
}
👉 ContentMode
- resizable 를 적용하면 자신이 주어진 공간 내에서 최대한의 크기만큼 확장하려는 합니다. 단 비율에 신경 쓰지 않고 공간을 가득 채우로고 함 그래서 ContentMode 를 사용하여 비율을 조절합니다
UIKit | SwiftUI | 설명 |
---|---|---|
Scale to Fill | 기본값 | 비율과 관계없이 이미지를 늘려서 주어진 공간을 가득 채우게 합니다 |
Aspect Fit | .scaleToFit() | 이미지 원본의 비유을 그대로 유지한 상태에서, 가능한 최대 크기까지 늘어 납니다. 이때 최대 크기는 주어진 공간의 너비와 높이 중 작은 값을 기준으로 합니다 |
Aspect Fill | .scaleToFill() | 이미지 원본의 비율을 그대로 유지한 상태에서, 가능한 최대 크기까지 늘어납니다. 이때, 최대 크기는 주어진 공간의 너비와 높이 중 큰 값을 기준으로 합니다. 따라서 이미지의 일부가 주어진 공간을 벗어나 더 크게 표현될 수 있습니다 |
// scaledToFit, scaledToFill 수식어를 이용해 콘텐츠 모드를 변경할 수 있습니다.
struct Example03: View {
var body: some View {
HStack(spacing: 30) {
// UIKit의 Scale To Fill 이 기본값으로 적용
Image("SwiftUI").resizable().frame(width: 100, height: 150)
// UIKit의 Aspect Fit 효과 적용
Image("SwiftUI").resizable().scaledToFit().frame(width: 100, height: 150)
// UIKit 의 Aspect Fill 효과 적용
Image("SwiftUI").resizable()
.scaledToFill()
.frame(width: 100, height: 150)
.clipped() // UIView의 ClipsToBounds 활성화. 프레임을 벗어나는 이미지 제거
}
}
}
👉 AspectRatio
-
이미지의 비율에 대해 좀 더 세붖덕인 조정이 필요하다면 aspectRatio 수식어를 사용해서 이미지의 너비와 노ㅠ이의 비율을 조정합니다
-
parameter 으로 CGFloat(너비 / 높이를 계산해서 그 비율을 전달), CGSize(width 와 height 에 각각 원하는 값을 입력합니다)
// aspectRatio를 통해 세부적인 비율 조정이 가능합니다.
struct Example04: View {
var body: some View {
// scaleToFit 콘텐츠 모드를 적용한 뒤, 너비가 높이보다 1.6배의 비율을 가지도록 조정
HStack(spacing: 30) {
Image("SwiftUI").resizable()
.aspectRatio(CGSize(width: 1.6, height: 1), contentMode: .fit)
.frame(width: 150, height: 150)
// scaledtoFill 콘텐츠 모드 적용한 뒤, 너비가 높이보다 0.7배의 비율을 가지도록 조정
Image("SwiftUI").resizable()
.aspectRatio(0.7, contentMode: .fill)
.frame(width: 150, height: 150).clipped()
}
}
}
👉 ClipShape
- clipShape 수식어를 이용해 이미지를 원하는 모양으로 만들 수도 있습니다. 도형의 크기는 이미지의 크기를 기준으로 생성되지만 원하는 크기로 조정하는 것도 가능합니다
// clipShape - 지정한 Shape 의 모습으로 이미지 잘라내기
struct Example05: View {
var body: some View {
HStack(spacing: 20) {
Image("SwiftUI").clipShape(Circle()) // 원
Image("SwiftUI").clipShape(Rectangle().inset(by: 10)) // 이미지 크기보다 사방으로 10씩 크기를 줄인 사각형
// 크기와 위치를 직접 지정한 타원
Image("SwiftUI").clipShape(Ellipse().path(in: CGRect(x: 10, y: 10, width: 80, height: 110)))
}
}
}
👉 RenderingMode
-
이미지에는 template 과 original 이렇게 2가지 랜더일 모드가 사용되니다
-
template : 이미지의 불투명 영역이 가진 본래의 색을 무시하고 원하는 색으로 변경해 템플릿 이미지로 활용할 수 있게 합니다
-
original : 항상 이미지 본래의 색을 유지합니다
-
// original / template 렌더링 모드를 지정해 줄 수 있습니다.
struct Example06: View {
var body: some View {
HStack {
Image("SwiftUI") // 랜더링 모드 생략 시 시스템이 결정
Image("SwiftUI").renderingMode(.original) // 원본 이미지 색상 유지
Image("SwiftUI").renderingMode(.template) // template 색상으로 변경
}
.foregroundColor(.red)
}
}
👉 SF Symbols
- SF Symbols 는 백터 기반의 이미지로 만들어져 이미지의 색을 쉽게 변경할 수 있으며, 크기 변화에 자유롭습니다
// SF Symbol은 Image(systemName:) 생성자를 사용합니다.
struct Example07: View {
var body: some View {
HStack(spacing: 20) {
Image(systemName: "star.circle").resizable().frame(width: 100, height: 100)
Image(systemName: "star.circle.fill").resizable().frame(width: 100, height: 100)
}
}
}
👉 ImageScale
- 각 심벌은 imageScale 수식어를 적용해서 small, medium, large 를 선택 할 수 있습니다. 기본값은 medium 이고, 각 심벌에 원하는 색을 입힐 수 도 있습니다i
// imageScale을 이용하면 크기를 변경할 수 있습니다.
struct Example08: View {
var body: some View {
HStack(spacing: 30) {
Image(systemName: "book.fill").imageScale(.small).foregroundColor(.red)
Image(systemName: "book.fill").foregroundColor(.green) // medium 기본값
Image(systemName: "book.fill").imageScale(.large).foregroundColor(.blue)
}
}
}
👉 Font
- ImageScale 이외에도 font 를 이용해 크기를 변경하는 것도 가능합니다
// font를 이용해 심벌의 크기를 조절 가능합니다.
struct Example09: View {
var body: some View {
HStack(spacing: 30) {
Image(systemName: "speaker.3").font(.body)
Image(systemName: "speaker.3").font(.title)
Image(systemName: "speaker.3").font(.system(size: 40))
Image(systemName: "speaker.3").imageScale(.large).font(.system(size: 40))
}
}
}
👉 Weight
- 이미지에는 굴기를 바로 조정할 수가 없어서 font 를 먼저 사용해서 변경해야 합니다
// fontWeight를 이용해 심벌의 굵기를 조절 가능합니다.
struct Example10: View {
var body: some View {
HStack(spacing: 30) {
Image(systemName: "arrow.up").font(Font.title.weight(.black))
Image(systemName: "arrow.left").font(Font.title.weight(.semibold))
Image(systemName: "arrow.right").font(Font.title.weight(.light))
Image(systemName: "arrow.down").font(Font.title.weight(.ultraLight))
}
}
}
🔷 View Layout
👉 Stack
-
UIkit 의 UIStackView 와 같은 역활로써 SwiftUI 에서는 필수적으로 활용되는 중요합니다
-
가로방향(HStack), 세로방향(VStack), 계층방향(ZStack) 와 같이 3가지 종류의 스택이 있습니다
// HStack, VStack, ZStack 세 가지 종류의 스택이 있습니다.
struct Example01: View {
var body: some View {
VStack(spacing: 50){
HStack {
Rectangle().fill(Color.green).frame(width: 100, height: 100)
Rectangle().fill(Color.yellow).frame(width: 100, height: 100)
}
VStack {
Rectangle().fill(Color.green).frame(width: 100, height: 100)
Rectangle().fill(Color.yellow).frame(width: 100, height: 100)
}
ZStack {
Rectangle().fill(Color.green).frame(width: 100, height: 100)
Rectangle().fill(Color.yellow).frame(width: 100, height: 100).offset(x: 40, y: 40)
}
}
}
}
spacing, argument
-
spacing 을 매개변수의 값을 조정하여 뷰의 간격을 지정해 줄 수 있습니다.
-
argument 를 통해 자식 뷰의 정렬 위치를 지정할 수 있습니다
// alignment 매개 변수를 통해 자식 뷰의 정렬 위치를 지정할 수 있습니다.
struct Example02: View {
var body: some View {
HStack(alignment: .top) {
Rectangle().fill(Color.green).frame(width: 150, height: 150)
Rectangle().fill(Color.yellow).frame(width: 150, height: 550) // heigth 를
}
}
}
수식어 활용
- 스택에도 뷰 프로토콜이 가진 수식어를 적용할 수 있습니다.
// 컨테이너 뷰 자체에 영향을 미치는 수식어가 있고 자식 뷰에게 반영되는 수식어가 있습니다.
struct Example03: View {
var body: some View {
HStack {
Text("HStack").font(.title).foregroundColor(Color.blue)
Text("은 뷰를 가로로 배열합니다")
Text("!")
}.padding().border(Color.black) // 여백을 확보하고 검은색 테두리 그리기
}
}
스택 조합해서 도형만들기
// 여러 가지 스택을 조합하여 원하는 레이아웃을 구성할 수 있습니다.
struct Example04: View {
var body: some View {
VStack{
Text("도형 만들기").font(.largeTitle).fontWeight(.heavy)
HStack {
Text("둥근 모양").font(.title)
Spacer() // 가장 좌측에 배치되도록 하기
}
ZStack {
Rectangle().frame(height: 10) // 사각형의 높이를 10으로 주어 선으로 사용
HStack{
Circle().fill(Color.yellow) // 원
Ellipse().fill(Color.green) // 타원
Capsule().fill(Color.orange) // 캡슐
RoundedRectangle(cornerRadius: 30).fill(Color.gray) // 둥근 모서리 사각형
}
}
HStack{
Text("각진 모양").font(.title)
Spacer()
}
ZStack {
Rectangle().frame(height: 10)
HStack {
Color.red // SwiftUI 에서는 컬러 그 자체도 하나의 뷰에 해당합니다
Rectangle().fill(Color.blue) // 사각형
RoundedRectangle(cornerRadius: 0).fill(Color.purple) // CornerRadius 를 0으로 준 사각형
}
}
}.padding()
}
}
👉 Spacer
- Spacer 는 뷰 사이의 간격을 설정하거나 뷰의 크기를 확장할 용도로 사용되는 레이아웃을 위한 뷰입니다
// Spacer 는 가능한 최대 크기만큼 공간을 확보하지만, frame 으로 크기를 제한 할 수 있습니다
struct Example01: View {
var body: some View {
VStack(spacing: 50) {
HStack {
Spacer() // 최대크기만큼 늘림
Text("Spacer").font(.title).background(Color.yellow)
}.background(Color.blue)
HStack {
Spacer().frame(width: 100) // spacer 크기 조절
Text("Spacer").font(.title).background(Color.yellow)
}.background(Color.blue)
}
}
}
- spacer 는 사용 가능한 뷰의 크기에 따라 간격을 알아서 조절하는데, minLength 의 값을 설정하면 최소 간격을 지정할 수 있습니다. 더 큰 크기로 늘어나는 건 관계 없으나 더 작아 지지는 않습니다
// minLength 매개 변수로 Spacer의 최소 길이를 지정할 수 있습니다.
struct Example02: View {
var body: some View {
VStack (spacing: 50) {
HStack {
Text("Spacer MinLength").font(.title).foregroundColor(.white)
Spacer(minLength: 100) // minLength 100
Text("Spacer").font(.title).background(Color.yellow)
}.background(Color.blue)
HStack {
Text("Spacer MinLength").font(.title).foregroundColor(.white)
Spacer() // minLength 미지정
Text("Spacer").font(.title).background(Color.yellow)
}.background(Color.blue)
}
}
}
- 단, ZStack 에서 Spacer 는 다른 형제 뷰의 최대 크기만큼만 확장되기 때문에, ZStack 에서는 Cole.clear 나 Rectangle 처럼 부모뷰의 크기만큼 커지려는 확장성을 가진 뷰를 spacer 대신 사용할 수 있습니다
// ZStack에서 사용된 Spacer는 스택 외부에서 단독으로 사용되었을 때와 동일하게 동작합니다.
// ZStack에서 공간을 확장하기 위해서는 Spacer 대신 Color.clear, Rectangle() 등을 사용할 수 있습니다.
struct Example03: View {
var body: some View {
ZStack {
Color.clear
Text("Spacer").font(.title).background(Color.yellow)
}.background(Color.blue)
}
}
👉 Overlay and Background
- Overlay 와 Background 수식어를 사용하면 ZStack 처럼중첩된 뷰를 표현하는것이 가능합니다
Overlay
- Overlay 는 뷰 원본으 ㅣ공간을 기준으로 그 위헤 새로운 뷰를 중첩하여 쌓는 기능을 합니다. UIKit 에서 addSubView 메서드를 사용하는 개념과 같은데, 자식 뷰를 추가하면 부모 뷰를 기준으로 프레임의 좌표가 결정되고 그 크기도 영향을 받았던 것과 동일 합니다
// MARK: Example 01
// overlay 는 뷰의 상위 계층에 중첩하여 쌓는 수식어 입니다.
struct Example01: View {
var body: some View {
Rectangle().fill(Color.green).frame(width: 150, height: 150, alignment: .center)
.overlay(Rectangle().fill(Color.yellow).offset(x: 20, y: 20)) // 초록색 위로 사각형 위로 뷰 추가, 크기를 정해주지 않으면 초록색 사각형과 동일 크기
}
}
Background
-
background 수식어는 overlay 와 마찬가지로 뷰 원본의 공간을 기준으로 뷰를 중첩하는것은 같지만, 위가 아니라 그 아래 방향으로 하나씩 쌓아 나간다는 점이 다릅니다
-
overlay 는 위로, background 는 아래로 쌓인다는 점을 유의 해야 합니다
// background 는 뷰의 하위 계층에 중첩해서 쌓는 수식어 입니다.
struct Example02: View {
var body: some View {
Rectangle().fill(Color.yellow).frame(width: 150, height: 150, alignment: .center)
.background(Rectangle().fill(Color.green).offset(x: 20, y: 20)) // 노란색 사각형 아래에 새로운 뷰 추가
}
}
Alignment
- overlay 와 background 는 공통으로 alignment parameter 가 있어서 추가되는 뷰의 위치를 설정해 줄 수 있습니다.
// alignment 를 이용해 뷰의 위치를 결정할 수 있습니다.
struct Example03: View {
var body: some View {
Circle().fill(Color.yellow.opacity(0.8)).frame(width: 250, height: 250, alignment: .center)
// overlay
.overlay(Text("Joystick").font(.largeTitle))
.overlay(Image(systemName: "arrow.up").font(.title).padding(), alignment: .top)
.overlay(Image(systemName: "arrow.left").font(.title).padding(), alignment: .leading)
.overlay(Image(systemName: "arrow.up.right.circle.fill").font(.title), alignment: .topTrailing)
// background
.background(Image(systemName: "arrow.down").font(.title).padding(), alignment: .bottom)
.background(Image(systemName: "arrow.right").font(.title).padding(), alignment: .trailing)
}
}
- ZStack 을 사용해서 위의 그림과 똑같이 그릴 수 있습니다
// ZStack 을 사용해서 위의 그림과 똑같이 그리기
struct Example04: View {
var body: some View {
ZStack {
VStack {
Spacer()
Image(systemName: "arrow.down").font(.title).padding()
}
HStack {
Spacer()
Image(systemName: "arrow.right").font(.title).padding()
}
Circle()
.fill(Color.yellow.opacity(0.8))
.frame(width: 250, height: 250)
Text("JoyStick").font(.largeTitle)
ZStack(alignment: .topTrailing) {
Color.clear
Image(systemName: "arrow.up.right.circle.fill").font(.title)
}
VStack {
Image(systemName: "arrow.up").font(.title).padding()
Spacer()
}
HStack {
Image(systemName: "arrow.left").font(.title).padding()
Spacer()
}
}.frame(width: 250, height: 250, alignment: .center)
}
}
.overlay/.background vs ZStack
📌 .overlay/.background: 전체 화면의 레이아웃을 구성 할때 사용되기 보다 UI 의 각 부분을 구성하는 개별적인 뷰 객체들을 꾸밀 때 활용됩니다
📌 ZStack: 상대적으로 직접적인 연관성이 없는 뷰들을 계층 구조로 나열하여 UI 를 구성할 때 사용합니다. 특정 콘텐츠의 변경 사항이 다른 뷰까지 함께 영향을 줄 수가 있다는 것을 유의 해야 합니다
🔶 🔷 📌 🔑 👉
🗃 Reference
Views and Controls - https://developer.apple.com/documentation/SwiftUI/Views-and-Controls
스윗한 SwiftUI - https://book.jacobko.info/#/book/1190014815
Leave a comment