포스트

MapKit (1)

이번엔 MapKit, CloudKit을 활용한 지도앱을 만들어본다.

기본적인건 최대한 생략을 해보는걸로…

프로젝트에 클라우드 기능 추가하기

프로젝트를 생성하고

CleanShot 2024-12-20 at 09 56 26

iCloud를 추가해준다.

CleanShot 2024-12-20 at 09 58 48

그리고 클라우드킷을 체크해주면 Container를 추가하는 창이 뜨는데 + 버튼을 눌러 추가해주자.

CleanShot 2024-12-20 at 09 59 57

App group 컨테이너 추가하듯 Identifier를 복사해서 붙이고, 앞에 icloud를 적어주었다.

그러면 CloudKit을 사용할 준비는 끝났다.

Color Asset 추가

주로 사용할 색상에 대해 추가를 해준다.

CleanShot 2024-12-20 at 10 05 14

+를 클릭하고 Color Set을 추가해주자

그리고 이름은 brandPrimary로 해주었다.

CleanShot 2024-12-20 at 10 06 21

그리고 순서대로 하여 색상을 추가해준다.

우측의 다크모드도 똑같이 적용해주자.

Color Extension 설정하기 (17버전 이상은 X)

1
2
3
extension Color {
    static let brandPrimary = Color("brandPrimary")
}

17버전 이전에는 Asset에 추가한걸 편하게 사용하기위해 이렇게 변수를 선언했지만,

17버전 이후에는 Asset에 추가를 하면

1
2
3
4
5
6
7
8
#if canImport(SwiftUI)
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
extension SwiftUI.Color {

    /// The "brandPrimary" asset catalog color.
    static var brandPrimary: SwiftUI.Color { .init(.brandPrimary) }

}

이렇게 자동으로 추가가 된다.

TabView 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
TabView {
    Tab("Map", systemImage: "map") {
        LocationMapView()
    }
    
    Tab("Locations", systemImage: "building") {
        LocationListView()
    }
    Tab("Profile", systemImage: "person") {
        ProfileView()
    }
}
.tint(.brandPrimary)

크게 언급할건 없어보인다.

LocationMapView 기본적인 디자인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// before 17
@State private var region = MKCoordinateRegion(
    center: CLLocationCoordinate2D(latitude: 37.331516, longitude: -121.891054),
    span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))

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

        VStack {
            Image("ddg-map-logo")
                .resizable()
                .scaledToFit()
                .frame(height: 70)
                .shadow(radius: 10)
            
            Spacer()
        }
    }
}

// after 17

현재 coordinateRegion은 Deprecated 되었다.

수정코드는 이후에 올리는걸로.

span의 경우 값이 클수록 지도가 축소된다.

나머지 기본적인 UI디자인은 생략

Text 짤릴때 팁

1
2
3
4
5
Text("Location Name")
                    .font(.title2)
                    .fontWeight(.semibold)
                    .lineLimit(1)
                    .minimumScaleFactor(0.75)

CleanShot 2024-12-20 at 13 42 47

Text가 길지도 않음에도 불구하고 이렇게 … 으로 생략된다면

1
2
3
4
5
6
Text("Location Name")
                    .font(.title2)
                    .fontWeight(.semibold)
                    .lineLimit(1)
                    .minimumScaleFactor(0.75)
                    .frame(maxWidth: .infinity, alignment: .leading)

CleanShot 2024-12-20 at 13 43 57

최대 가로 길이를 동적으로 바꿔준다.

alignment를 설정한건 좌측정렬을 해주기 위함.

Text 구성 팁

1
2
3
4
5
VStack(alignment: .leading, spacing: 8) {
                Text("Bio: 100 Characters Remain")
                    .font(.callout)
                    .foregroundStyle(.secondary)
}

CleanShot 2024-12-20 at 14 00 19

Text에 대해 Bio: 100 Characters Remain라는 문장에서, 단어별로 포인트를 주고싶을때는

아래와 같이 +_를 사용하면 Text 끼리 연결이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VStack(alignment: .leading, spacing: 8) {
    Text("Bio: ")
        .font(.callout)
        .foregroundStyle(.secondary)
    +
    Text("100")
        .bold()
        .font(.callout)
        .foregroundStyle(.brandPrimary)
    +
    Text(" Characters Remain")
        .font(.callout)
        .foregroundStyle(.secondary)
    
    // 생략
}

CleanShot 2024-12-20 at 14 02 55

CustomModifier 적용하기

1
2
3
4
5
6
7
8
TextField("First Name", text: $firstName)
    .font(.system(size: 32, weight: .bold))
    .lineLimit(1)
    .minimumScaleFactor(0.75)
TextField("Last Name", text: $lastName)
    .font(.system(size: 32, weight: .bold))
    .lineLimit(1)
    .minimumScaleFactor(0.75)

이렇게 같은 모디파이어가 중복이 될때 CustomModifier를 통해 간단하게 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
struct ProfileNameText: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.system(size: 32, weight: .bold))
            .lineLimit(1)
            .minimumScaleFactor(0.75)
    }
}

// 적용
TextField("First Name", text: $firstName)
    .modifier(ProfileNameText())

여기서 조금 더 Modifier 스럽게 바꾼다면

1
2
3
4
5
extension View {
    func profileNameStyle() -> some View {
        self.modifier(ProfileNameText())
    }
}

이렇게 extension으로 해준다.

1
2
TextField("Last Name", text: $lastName)
    .profileNameStyle()

이젠 Modifier처럼 사용이 가능하다.

기본적인 UI디자인 파트가 많아서 생략을 많이했다.

simulator_screenshot_6F1C4B51-D30F-484D-A71F-FF434BA42BC9simulator_screenshot_BAFB564D-A870-457B-AFEC-B8E5B57C163Bsimulator_screenshot_790DDDBA-EAEA-44DD-B884-4CEC6C5BA86C

완성된 초기 UI는 위와 같다.


Github: Dub-Dub-Grub Repository

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.