반응형

스크린샷처럼 특정 장소에 대한 설명, 사진, 지도를 한 화면에 보여주는 뷰를 만들어 봅니다.

설명, 사진, 지도를 보여주는 뷰를 각각 만들어서 마지막에 하나로 결합합니다.

 

Introducing SwiftUI( https://developer.apple.com/tutorials/SwiftUI ) 를 따라해보면서 정리한 문서입니다.

 

참조한 문서와 달리 inspector를 사용하지 않고 코드를 추가하며 진행합니다. 



2021. 1. 9 - 최초작성



 

Introducing SwiftUI 따라잡기 - 장소 소개하는 화면 만들기

프로젝트 생성

 

장소 설명을 위한 뷰 추가하기

 

이미지를 보여주기 위한 뷰 추가하기

 

지도를 보여주기 위한 뷰 추가하기

 

Detail View 만들기

 

Building Lists and Navigation



프로젝트 생성

 

1. Xcode를 실행하여 시작 윈도우에서 "Create a new Xcode project"를 클릭하거나 메뉴에서 File > New > Project를 선택합니다.

 




2. 플랫폼으로 iOS를 선택하고, Application 항목에 있는 App 템플릿을 선택합니다. 이제 Next를 클릭합니다.

 



3. Product Name에 Landmarks를 입력하고 Organization Identifier에 스크린샷을 참고하여 입력합니다. 

Interfaces는 SwiftUI를 선택하고, Language는 Swift를 선택합니다.  Next를 클릭합니다. 

 



New Folder 버튼을 클릭하여 원하는 위치에 Landmarks 프로젝트를 저장할 폴더를 생성한 후 스크린샷처럼 해당 폴더가 선택된 상태에서 Create 버튼을 클릭합니다.   폴더를 새로 생성하지 않으면 현재 보이는 위치에 Landmarks 프로젝트를 위한 폴더가 생성됩니다. 

 



앞에서 지정한 위치에 새로 생성된 프로젝트가 Xcode에 열립니다.

 



4. 왼쪽에 보이는 Project navigator에서 LandmarksApp를 클릭하면 LandmarksApp.swift 파일이 열립니다. 

 



SwiftUI 앱 라이프 사이클(app life cycle)을 사용하는 앱은 앱 프로토콜을 준수하는 구조를 가지고 있습니다.

구조체의 body 속성은 하나 이상의 scenes을 리턴합니다.  차례로 표시할 콘텐츠를 제공합니다.

 

@main 속성은 앱에서 처음 실행되는 코드인 진입점(entry point)을 지정합니다.  



import SwiftUI

@main
struct LandmarksApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}



5. Project navigator에서 ContentView를 클릭하면 ContentView.swift 파일이 열립니다. 

 



디폴트로 SwiftUI  view 파일에는 두 개의 구조체가 선언됩니다. 첫번째 구조체 ContentView는 View 프로토콜를 채택하며 View의 내용과 레이아웃을 정의합니다. 두번째 구조체 ContentView_Previews는 ContentView 뷰에 대한 미리보기를 선언합니다.  



6. 코드 창 옆에 있는 Canvas에 보이는 Resume를 클릭합니다. 

 



미리보기 화면이 보입니다. 

 




장소 설명을 위한 뷰 추가하기

 

1. 이제 코드를 수정해봅니다. 수정한 부분은 초록색 배경으로 표시됩니다. 

 

body 속성에 있는 문자열을 원하는 것으로 변경해봅니다. 여기에서 안녕하세요로 변경했습니다. 

변경하자마자 캔버스의 미리보기 화면에 반영됩니다.



import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("안녕하세요~")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}





2. padding() 메소드를 추가했는지 여부에 따라 문자열 주변에 여백의 공간을 얼마나 줄지가 달라집니다. 

 




3. 글자 폰트를 title 으로 변경하면 글자 크기가 커집니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("안녕하세요~")
            .font(.title)
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 



4. 글자색은 빨간색, 글자배경색은 노란색으로 변경합니다. 

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("안녕하세요~")
            .font(.title)
            .foregroundColor(.red)
            .background(.yellow)
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 




5. 앞에서 문자열을 보여주기 위해 사용한 View의 body 속성은 하나의 view만 리턴하기 때문에 하나의 문자열만 표시할 수 있습니다. 여러 개의 문자열을 하나의 화면에 표시하려면 스택을 사용하여 여러 view를 묶어서 보여줘야 합니다. 

 

이제부터 수정해볼 코드는 다음 화면처럼 첫번째 줄에는 Turtle Rock을 출력하고 두번째 줄에는 Joshua Tree National Park와 California를 출력합니다. 두 줄을 표시하기 위해 VStack을 사용하고 두번째 줄에 두 개의 문자열을 출력하기 위해 HStack을 사용합니다. 두번째 줄의 두 개의 문자열 사이에 간격을 띄기 위해 Spacer를 사용합니다. 

 



6. 광화문의 위치를 소개하는 내용으로 변경해봅니다. 

 

VStack을 사용하여 두 개의 문자열을 2줄로 출력해봅니다. 

장소 이름인 “광화문”은 좀 크게 보이게 하기 위해 title 폰트를 사용하고  장소의 주소인 "서울 종로구 사직로 161"는 상대적으로 작게 보이게 하기 위해 subheadline 폰트를 사용합니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack{
            Text("광화문")
                .font(.title)
            Text("서울 종로구 사직로 161")
                .font(.subheadline)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



두 개의 문자열이 중앙 정렬되어 출력됩니다.




7. VStack에 포함된 두 문자열의 첫번째 글자의 위치를 맞추어 정렬하도록 합니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(alignment:.leading){
            Text("광화문")
                .font(.title)
            Text("서울 종로구 사직로 161")
                .font(.subheadline)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

두 문자열의 첫번째 글자 출력 위치가 같도록 조정되었습니다. 

 




8. 광화문의 위치를 설명하는 한국이라는 문자열을 하나 더 추가해봅니다.

두 번째 줄에 있는 장소 주소 옆에  subheadline 폰트를 사용하도록 배치하려고 합니다. 

 

HStack에 가로 한줄에 표시할 두 개의 문자열을 다음처럼 배치하면 됩니다. 

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(alignment:.leading){
            Text("광화문")
                .font(.title)
           
            HStack{
                Text("서울 종로구 사직로 161")
                    .font(.subheadline)
                Text("한국")
                    .font(.subheadline)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



광화문 주소 옆에 한국이 표시되었습니다. 

 




9. 광화문 주소와 한국이 너무 붙어있으니 그 사이에 간격을 추가해봅니다. 

두 개의 문자열 사이에 Spacer()를 추가하면 됩니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(alignment:.leading){
            Text("광화문")
                .font(.title)
           
            HStack{
                Text("서울 종로구 사직로 161")
                    .font(.subheadline)
                Spacer()
                Text("한국")
                    .font(.subheadline)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



광화문 주소와 한국 사이에 공백이 추가되었지만 너무 양쪽 문자열들이 붙어버렸습니다.

 



10. VStack에 padding()를 추가하면 이 문제를 개선할 수 있습니다. 

여기에선 padding()의 아규먼트로 50을 주어서 보기좋게 문자열들이 보이도록 조정했습니다. 이 값을 변경하며 변화를 확인해보세요. 

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(alignment:.leading){
            Text("광화문")
                .font(.title)
           
            HStack{
                Text("서울 종로구 사직로 161")
                    .font(.subheadline)
                Spacer()
                Text("한국")
                    .font(.subheadline)
            }
        }.padding(50)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}







이미지를 보여주기 위한 뷰 추가하기

 

1. 광화문 이미지를 보여주기 위해 사용할 View를 추가합니다. 

 

이미지를 원형으로 자른 것처럼 일부만 보여주기 위해 Clip shape를 사용하고, 원 가장자리에 테두리를 그려주기 위해 Circular border를 사용합니다.  




2. 인터넷에서 광화문 이미지 파일을 하나 다운로드 받아 Xcode의 Assets를 선택 후, 오른쪽에 보이는 공간에 이미지 파일을 드래그앤드롭합니다. 아래 화면처럼 해당 이미지 파일이 보이게 됩니다. 

 



3. 이미지 파일을 보여줄 SwiftUI view를 하나 더 생성합니다.

메뉴에서 File > New > File… 를 선택합니다.



iOS 탭에 있는 SwiftUI View가 선택된 상태에서 Next 버튼을 클릭합니다. 

 



Save As에 CircleImage.swift를 입력한 후, Create 버튼을 클릭합니다.

 




4. 앞에서 추가한 이미지를 보여주는 코드를 추가합니다. 문자열 “광화문” 은 앞에서 추가한 이미지 파일의 이름으로 변경해야 합니다.

( 참고 https://sarunw.com/posts/how-to-resize-swiftui-image-and-keep-aspect-ratio/ )

 

이미지가 커서 화면에 맞추기 위해서 resizable()와 scaledToFit()를 추가했습니다.

 

import SwiftUI


struct CircleImage: View {

    var body: some View {

            Image("광화문")
            .resizable()
            .scaledToFit()
    }

}


struct CircleImage_Previews: PreviewProvider {

    static var previews: some View {

            CircleImage()

    }

}

 





5. 원형으로 이미지를 잘라내기 위해서 clipShape(Circle())를 추가합니다.

 

import SwiftUI


struct CircleImage: View {

    var body: some View {

            Image("광화문")
            .resizable()
            .scaledToFit()
            .clipShape(Circle())
    }

}


struct CircleImage_Previews: PreviewProvider {

    static var previews: some View {

            CircleImage()

    }

}

 




6. 원형으로 잘라진 그림의 가장자리에 회색선을 표시하기 위해 회색선 원을 추가합니다. 

 

import SwiftUI


struct CircleImage: View {

    var body: some View {

            Image("광화문")
            .resizable()
            .scaledToFit()
            .clipShape(Circle())
            .overlay {

                Circle().stroke(.gray, lineWidth: 4)

            }
    }

}


struct CircleImage_Previews: PreviewProvider {

    static var previews: some View {

            CircleImage()

    }

}

 





7. 7포인트 반경의 그림자를 회색원 주변에 추가합니다.

 

import SwiftUI


struct CircleImage: View {

    var body: some View {

            Image("광화문")
            .resizable()
            .scaledToFit()
            .clipShape(Circle())
            .overlay {

                Circle().stroke(.gray, lineWidth: 4)

            }
            .shadow(radius: 7)
    }

}


struct CircleImage_Previews: PreviewProvider {

    static var previews: some View {

            CircleImage()

    }

}

 




8. 이미지 가장자리에 추가했던 회색원의 색깔을 흰색원으로 변경합니다.

 

import SwiftUI


struct CircleImage: View {

    var body: some View {

            Image("광화문")
            .resizable()
            .scaledToFit()
            .clipShape(Circle())
            .overlay {

                Circle().stroke(.white, lineWidth: 4)

            }
            .shadow(radius: 7)
    }

}


struct CircleImage_Previews: PreviewProvider {

    static var previews: some View {

            CircleImage()

    }

}

 



지도를 보여주기 위한 뷰 추가하기

 

1. 지도를 보여줄 SwiftUI view를 생성합니다.

메뉴에서 File > New > File… 를 선택합니다.



iOS 탭에서 SwiftUI View가 선택된 상태에서 Next 버튼을 클릭합니다. 

 



Save As에 MapView.swift를 입력한 후, Create 버튼을 클릭합니다.

 



2. MapKit를 임포트하기 위한 코드를 추가합니다.

 

import SwiftUI
import MapKit

struct MapView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}



2. 지도를 위한 지역 정보를 저장할 private  상태 변수를 생성합니다. 

 

You use the @State attribute to establish a source of truth for data in your app that you can modify from more than one view. 

 

@State 속성을 사용하여 하나 이상의 뷰에서 수정할 수 있는 앱의 데이터에 대한 정보 소스를 설정합니다.

 

SwiftUI manages the underlying storage and automatically updates views that depend on the value.

 

SwiftUI는 기본 저장소를 관리하고 값에 의존하는 뷰를 자동으로 업데이트합니다.



import SwiftUI
import MapKit

struct MapView: View {
   
   @State private var region = MKCoordinateRegion(

        center: CLLocationCoordinate2D(latitude: 37.57593761336783, longitude:126.9768248852631),

        span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)

    )
   
    var body: some View {
        Text("Hello, World!")
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}




3. 디폴트로 있던 Text 뷰를 region을 바인딩하는 Map 뷰로 변경합니다. 



By prefixing a state variable with $, you pass a binding, which is like a reference to the underlying value. 

상태 변수에 $를 접두사로 붙이면 기본 값에 대한 참조와 같은 바인딩을 전달합니다.

 

사용자가 지도와 상호작용할 때 지도는 사용자 인터페이스에 현재 표시되는 지도 부분과 일치하도록 region 변수의 값을 업데이트합니다.




import SwiftUI
import MapKit

struct MapView: View {
   
    @State private var region = MKCoordinateRegion(

        center: CLLocationCoordinate2D(latitude: 37.57593761336783, longitude:126.9768248852631),

        span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)

    )
   
    var body: some View {
       Map(coordinateRegion: $region)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}



지도 영역이 표시되는데 아직 실제로 지도가 보이지는 않습니다.

 




캔버스 상단에 보이는 

아이콘을 클릭해야 실제로 지도가 보입니다. 광화문 위치가 지도 중앙에  Gwanghwamun Gate라고 파란색으로 표시되어 있습니다. 

 



다시

버튼을 클릭하면 지도 영역만 다시 표시됩니다. 

 





Detail View 만들기

 

장소 설명을 위한 뷰, 이미지를 보여주기 위한 뷰, 지도를 보여주기 위한 뷰를 결합하여 Detail View를 만듭니다. 

아래 스크린샷과 같은 화면을 얻게 됩니다. 

 



1. 프로젝트 네비게이터에서 CententView.swift 파일을 선택합니다. 



2. VStack을 추가합니다.



import SwiftUI

struct ContentView: View {
    var body: some View {
       
        VStack
        {
            VStack(alignment:.leading){
                Text("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
            }.padding(50)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



3. height 파라미터를 지정하여 MapView를 추가합니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
       
        VStack
        {
            MapView()
                .frame(height: 300)

            VStack(alignment:.leading){
                Text("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
            }.padding(50)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



를 클릭하면 지도가 보입니다.

 




4. CircleImage를 추가합니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
       
        VStack
        {
            MapView()
                .frame(height: 300)

            CircleImage()
           
            VStack(alignment:.leading){
                Text("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
            }.padding(50)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 




5. CircleImage를 MapView 위에 겹쳐서 보여주기 위해서 CircleImage를 수직으로 -130포인트의 오프셋을 지정하고 CircleImage 하단에 -130포인트의 패딩을 지정합니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
       
        VStack
        {
            MapView()
                .frame(height: 300)

            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)
           
            VStack(alignment:.leading){
                Text("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
            }.padding(50)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 



6. VStack 하단에 Spacer를 추가하여 콘텐츠를 화면 상단으로 푸시합니다.

 

import SwiftUI

struct ContentView: View {
    var body: some View {
       
        VStack
        {
            MapView()
                .frame(height: 300)

            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)
           
            VStack(alignment:.leading){
                Text("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
            }.padding(50)
           
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}





7. MapView 위에 여백을 없애고 해당 영역에 지도가 보이도록합니다.

 

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("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
            }.padding(50)
           
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 





8. 광화문에 대한 설명을 추가합니다. 

 

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("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                        .font(.subheadline)
                    Spacer()
                    Text("한국")
                        .font(.subheadline)
                }
               
                Divider()


                Text("광화문은...")

                    .font(.title2)

                Text("경복궁의 남쪽에 있는 정문이다. '임금의 큰 덕(德)이 온 나라를 비춘다'는 의미이다.")
                    .fixedSize(horizontal: false, vertical: true)
               
            }.padding(50)
           
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 





9. 두 개의 텍스트를 HStack으로 묶은 후, 속성을 추가하는 것으로 변경합니다.

 

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("광화문")
                    .font(.title)
               
                HStack{
                    Text("서울 종로구 사직로 161")
                    Spacer()
                    Text("한국")
                }
                .font(.subheadline)
                .foregroundColor(.secondary)
               
                Divider()


                Text("광화문은...")

                    .font(.title2)

                Text("경복궁의 남쪽에 있는 정문이다. '임금의 큰 덕(德)이 온 나라를 비춘다'는 의미이다.")
                    .fixedSize(horizontal: false, vertical: true)
               
            }.padding(50)
           
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}





반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts