티스토리 뷰
배포한 개인앱에 위젯을 넣기 위해서 공부하려고 했으나 미루고 미루다가 이제는 SwiftUI를 시작해보려고 합니다!!
일단 경험해봐야 이론도 쉽게 익힐 수 있다고 생각하기 때문에 공식홈페이지에 있는 튜토리얼을 먼저 진행해보겠습니다!
https://developer.apple.com/tutorials/swiftui/creating-and-combining-views
Creating and Combining Views | Apple Developer Documentation
This tutorial guides you through building Landmarks — an app for discovering and sharing the places you love. You’ll start by building the view that shows a landmark’s details.
developer.apple.com
우선, 애플 공식문서에서 제공하는 튜토리얼은 랜드마크를 공유하고 발견하는 앱을 구현해보는 튜토리얼 이라고 합니다.
뷰를 배치하기 위해서 스택을 이용해 결합하여 레이아웃을 배치할 것이고
맵을 이용하기 위해 MapKit또한 이용할 것이고
View를 디자인하는 코드는 실시간으로 변환되어 직접 볼 수 있다고 써져있네요!!
그럼 시작해봅시다~!
Section1.
프로젝트 생성 및 캔버스 탐험
프로젝트를 SwiftUI를 사용하여 생성하고,캔버스, 미리보기 그리고 예제코드를 둘러보세요!
Xcode의 캔버스로 부터 미리보기를 볼 수 있고 이 모든 최신 기능을 사용하려면 macOS가 Monterey 이상의 버전이어야 한다고 합니다!!
Step1. Xcode를 열고 프로젝트를 생성해주세요.
Step2. iOS플랫폼에서 App템플릿을 고르고 Next 버튼을 눌러주세요.
Step3. ProductName을 자유롭게 설정하고 (문서에선 Landmark로 지었어요.) Interface는 SwiftUI / language는 Swift로 선택하고 Next버튼을 누른 후 프로젝트를 저장할 경로를 지정하여 프로젝트 생성을 완료해주세요.
Step4. 왼쪽 네비게이션 영역에서 {ProductName}App.swift 파일을 눌러 확인하세요. ( ex: LandmarksApp.swift )
SwiftUI라이프 사이클을 사용하는 앱은 앱 프로토콜을 준수하는 구조를 가지고있습니다.
그 구조의 Body는 하나 이상의 장면을 반환하며 이는 차례로 표시할 콘텐츠를 제공합니다.
@main은 앱의 시작점을 식별하는 속성(attribute)입니다.
하나 이상의 장면이라고 해서 테스트 뷰를 하나 더 추가하고 배경색을 지정해보았는데..
나는 가득 차서 반 반 나눠질 줄 알았는데 쪼그맣네... Inspactor보니까 고유 사이즈로 되어 있네요 일단 계속 진행해 봐야겠다..
Step5. 왼쪽 네비게이션 영역에서 ContentView.swift 파일을 선택하세요.
기본적으로 SwiftUI파일은 두개의 구조가 있습니다.
첫 번째 구조는 뷰 프로토콜을 준수하며 뷰의 내용과 레이아웃을 설명합니다.
두 번째 구조는 그 뷰에 대한 미리보기를 선언합니다.
Step6. 텍스트를 임의로 바꿔보라고 합니다!! 각자 해보시면 될 것 같아요! 바꾸는대로 미리보기에서 변환되시는것만 확인하고 넘어가면 됩니당
Section 2.
TextView 커스터마이징
코드를 변경하여 뷰를 커스터마이징 할 수 있습니다. 또는 인스펙터를 사용해서 사용 가능한 것을 발견하고 코드를 작성하는데 도움을 받을 수 있습니다.
이 예제 앱에서는 소스 편집기를 이용하거나 캔버스 또는 인스펙터 중 어떤 도구를 사용하든 코드가 계속 업데이트 됩니다.
1. 인스펙터 사용 방법
해당 뷰를 미리보기에서 클릭 후 오른쪽 영역에 인스펙터 영역을 확인하시면 됩니다.
또는 해당 뷰를 Command + 좌클릭시 Show SwiftUI Inspactor를 통해 확인하실 수 있고
해당 클릭이 작동하지 않는다면 미리보기 왼쪽 아래에 Selectable로 되어있는지 확인해보시면 좋을 것 같습니다!
그리고 위에 존재하는 속성들이 커스터마이징할 수 있는 속성들입니다.
Step 2
Step 3
Step 4
Text Inspactor 영역에서 Text를 TurtleRock으로 변경하고 Font modifier를 Title로 변경해줍니다!
그리고 Padding()코드를 지우고 Text아래 .foregroundColor(Color.green)를 입력하여 색상을 변경해줍니다.
Step5
Text를 Command + 좌클릭 Show SwiftUI Inspactor를 통해 다시 Color를 black으로 바꾸어줍니다.
위 과정을 통해 Inspector로 바꾸던 Code로 바꾸던 서로 일치된 결과로 업데이트 되는 것을 확인할 수 있습니당
Section 3.
스택을 사용한 뷰 결합
Section 2과정의 TitleView외에도 공원이름 / 상태 같은 세부 사항을 포함하는 텍스트뷰도 추가할 수 있습니다.
SwiftUI View를 만들때 Body속성에서 콘텐츠, 레이아웃 및 동작을 설명하지만 Body속성은 단일 뷰만 반환합니다.
수평, 수직, 또는 앞뒤로 뷰를 그룹화하는 스택에 여러가지 뷰를 결합하고 삽입할 수 있습니다.
이 섹션에서는 수직 스택을 사용하여 공원에 대한 세부 사항이 포함된 수평 스택 위에 제목을 배치할 것입니다.
Step1. TextView생성자를 Command-Click하고 Embed in VStack하여 Vstack에 넣어줍니다.
Step2. 우측 상단의 +버튼을 클릭하여 textView를 Vstack안에 TurtleRock textView 아래로 드래그합니다.
Step3.4. 추가한 TextView의 text를 Joshua Tree National Park로 설정하고 Font는 subheadline으로 변경해줍니다.
Step5. VStack의 생성자에 alignment를 추가하여 leading 으로 정렬해주세요. 기본값은 center에 정렬되어 있습니다.
다음으로 오른쪽에 상태를 나타낼 것입니다.
Step6. Joshua Tree National Park TextView를 Command-Click하고 HStack에 넣어줍니다.
Step7. 새 TextView를 HStack에 추가해주고 Font를 subheadline으로 변경해줍니다.
Step8. Spacer를 사용하여 두 텍스트 뷰 사이의 공간을 잡아주세요.
Step9. 마지막으로, padding() 수정자 메소드를 사용하여 랜드마크의 이름과 세부 사항에 조금 더 많은 공간을 부여해주세요.
var body: some View {
VStack {
VStack(alignment: .leading) {
Text("TurtleRock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
Section4.
커스텀 이미지뷰를 생성하세요.
다음은 랜드마크 이미지를 추가하는 작업입니다.
이 파일에 더 많은 코드를 추가하여 마스크, 테두리, 그림자를 이미지에 적용하는 커스텀 이미지뷰를 만들 수 있습니다.
https://developer.apple.com/tutorials/swiftui/creating-and-combining-views
위 사이트에서 프로젝트 파일을 다운로드하고 Resoruce폴더에 이미지 파일을 확인하세요.
Step1. 왼쪽 네비게이션 영역에 Assets를 클릭하고 이미지를 추가하세요.
Step2. File > New > File 을 열고 SwiftUI View를 CircleImage이름으로 생성하세요.
Step3. Body안에 Text를 지우고 Image(_:)생성자를 사용하여 이미지를 추가하세요.
var body: some View {
Image("turtlerock")
}
Step4. ClipShape 메소드를 호출하고 Circle()을 인수로 전달하여 이미지를 원형 모양으로 깎아주세요.
var body: some View {
Image("turtlerock")
.clipShape(Circle())
}
Step5.6. 흰새 선으로 원을 만든 다음, 이미지 테두리를 주기 위해 오버레이로 추가하고 그림자를 7 point Radius로 추가해주세요.
var body: some View {
Image("turtlerock")
.clipShape(Circle())
.overlay { Circle().stroke(.white, lineWidth: 4) }
.shadow(radius: 7)
}
Section5.
다른 프레임워크에서 SwiftUI View 사용하기
주어진 좌표를 중심으로 하는 지도를 만들 것입니다. MapKit의 지도 보기를 사용해 지도를 렌더링 할 수 있습니다.
Step1. map을 관리할 커스텀 뷰 생성하기 File > New > File 에서 iOS플랫폼 -> SwiftUI View Template 선택하고 MapView로 생성해주세요.
Step2. MapKit을 사용하기 위해 생성한 MapView에 import MapKit 코드를 추가해주세요.
Step3. 지도의 지역 정보를 보관하는 개인 상태 변수를 만드세요.
@State 속성을 사용하여 하나 이상의 View에서 수정할 수 있는 앱의 데이터에 대한 진실 소스를 설정합니다. SwiftUI는 기본 스토리지를 관리하고 값에 따라 뷰를 자동으로 업데이트합니다.
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
Step4. TextView를 지역에 바인딩하는 MapView로 바꾸세요.
var body: some View {
Map(coordinateRegion: $region)
}
상태 변수에 $ 접두사를 붙이면, 기본 값에 대한 참조와 같은 바인딩을 전달합니다. 사용자가 지도와 상호 작용할 때, 지도는 현재 사용자 인터페이스에서 볼 수 있는 지도의 부분과 일치하도록 지역 값을 업데이트합니다.
Section5 전체 코드
import SwiftUI
import MapKit
struct MapView: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
Section6.
상세뷰 구성하기
원형 이미지/ 이름과 장소 / 위치 지도 등 필요한 모든 구성요소를 갖추었습니다.
지금까지 사용한 도구를 사용해 커스텀뷰들을 결합하여 최종 디자인을 만들어보세요.
Step1. 왼쪽 네비게이션 영역에서 ContentView.swift를 클릭하세요.
Step2.3. 존재하는 Vstack을 또 다른 Vstack을 생성하여 포함시키고 Vstack최상단에 mapView를 height300으로 추가해주세요.
var body: some View {
VStack {
MapView()
.frame(height: 300)
VStack(alignment: .leading) {
Text("TurtleRock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
Step4. Live Preview를 클릭해 지도가 잘 렌더링 되는지 확인해보세요.
Step5. CircleImage를 Vstack에 추가해주세요.
Step6. 이미지 뷰의 상단에서 offSet을 수직으로 -130을 주고 바텀에서부터 패딩을 -130을 주세요. 이미지 위치 조정을 위함입니다!
var body: some View {
VStack {
MapView()
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("TurtleRock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
Step7. 외부 VStack에 Spacer를 두어 콘텐츠 화면을최상단으로 올려주세요.
Step8. 지도가 SafeArea를 무시하고 최상단에 배치하려면 ignoresSafeArea(edges: .top)을 추가해주세요.
Step9. 구분자와 추가설명 TextView를 추가해주세요.
Divider()
Text("About Turtle Rock")
.font(.title2)
Text("Descriptive text goes here.")
Step10. 마지막으로 서브 헤드라인을 HStack의 옵션으로 빼주고 secondary Color옵션을 추가해주면 끝입니다!!
최종코드
//
// ContentView.swift
// SwiftUI_Tutorial1_Landmarks
//
// Created by 신상우 on 2023/03/27.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.ignoresSafeArea(edges: .top)
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("TurtleRock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
Spacer()
Text("California")
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About Turtle Rock")
.font(.title2)
Text("Descriptive text goes here.")
}
.padding()
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
이렇게 처음 SwiftUI를 접해보았는데 UIKit과 비교했을때 되게 단순하고 쉬우면서 헷갈리기도 하네요.
마지막으로 튜토리얼을 진행하면서 좀 더 알아보면 좋겠다 싶은 것 2개 정도만 알아보고 마무리하겠습니다!
Some 키워드
직접 정리하려고 했는데.. 소들이님 글이 너무 잘 설명되어 있어서 첨부하겠습니다!
https://babbab2.tistory.com/158
SwiftUI) ContentView 이해하기 (1/2) - some View가 뭔가요?
안녕하세요~ 소들입니다 :> 아침형 인간이 되기 위한 주니어 개발자의 발악 1일차 출근(재택)시간보다 2시간 전에 일어나 운동도 하고 공부도 하는 중이다 이말입니다 하하 1일차 느낀점 일찍 일
babbab2.tistory.com
@state 속성
공식문서를 보면 SwiftUI가 관리하는 값을 읽고 쓸 수 있는 속성 래퍼 유형 이라고 적혀있습니다. 음 무슨 뜻일까요..
App,Scene,View에서 프로퍼티를 생성할때 @state속성을 붙이고 초기값을 주어 생성하라고 합니다.
그리고 memeberwise 생성자에서 설정하지 않게 private로 선언해야한다고 합니다. ( 충돌날 수 있음 )
SwiftUI에서 view는 구조체로 생성되기 떄문에 이후 값을 변경할 수 없습니다. 그러나 view에선 textview로부터 입력을 받았을 경우 등 값을 변경해야 할 경우가 있는데 이를 위해 만들어진게 @state 키워드 입니다.
struct TestView: View {
var b = 1
var body: some View {
Button("testbutton") { b = 2 }
}
}
위와 같이 변수를 선언할 경우 아래와 같은 경고가 뜨게 됩니다. 말 그대로 변경할 수 없다는 겁니다!
Cannot assign to property: 'self' is immutable
struct TestView: View {
@State private var b = 1
var body: some View {
Button("testbutton") { b = 2 }
}
}
그러나 위와 같이 @State를 작성하게 되면 프로퍼티 값을 변경하여도 아무 오류 없이 읽고 쓸 수 있습니다.
또한 공식문서에 State로 선언된 값이 변경되면 SwiftUI는 값에 의존하는 뷰 계층 구조의 일부를 업데이트한다고 합니다.
상태값에 접근이 필요한 뷰 계층 구조의 가장 높은 뷰에서 상태속성을 private로 선언하고
그런 다음 읽기 전용 접근을 위해 직접 또는 읽기-쓰기 접근을 위한 바인딩으로 접근이 필요한 하위 뷰와 상태를 공유한다면
모든 스레드에서 상태 속성을 안전하게 변경할 수 있다고 합니다.
정리하면
- 구조체에서 프로퍼티 값을 변경해야하는 변수앞에 사용한다.
- 이 프로퍼티에 접근하는 뷰 계층 중 가장 상위 뷰에서 private와 함께 선언하면 모든 스레드에서 안전하다.
- 값이 변경되면 일부 뷰 계층이 업데이트 된다.
왜 모든 뷰가 Struct인가?
첫째, 성능 요소가 있다고 합니다.
UIKit에서 UIView는 수많은 속성과 메소드들이 존재하는 클래스이며 우리는 이것을 class로 상속받아 사용합니다.
그 속성 중 배경속성 하나만 이용한다고 해도 모두 상속받아야하는 구조입니다.
반대로 SwiftUI는 구조체로 뷰를 생성합니다. 구조체이므로 모든 속성을 상속받아 이용 하는 것이 아니라 특정 속성을 사용해야 한다면ViewModifier를 사용해 속성이 추가된 새로운 뷰를 만드는 것입니다. 뷰가 아주 가벼워집니다!
둘째, 메모리 관리가 용이합니다.
구조체는 COW에 의해 실제 사용될때 메모리에 할당되어 효율적이고 메모리 누수의 위험이 없습니다.
'SwiftUI' 카테고리의 다른 글
[SwiftUI] Beyond scrollviews (0) | 2024.04.16 |
---|---|
[Swift UI] SwiftUI 개발 공식문서 튜토리얼 실습[3] (0) | 2023.03.30 |
[Swift UI] SwiftUI 개발 공식문서 튜토리얼 실습[2] (0) | 2023.03.28 |