포스트

MapKit (9)

LocationDetailView 기능 추가하기

ViewModel을 하나 더 만들어 준다.

그리고 LocationDetailView에 있던 변수들을 ViewModel에 옮겨준다.

1
2
3
4
5
6
7
8
9
let columns = [GridItem(.flexible()),
                GridItem(.flexible()),
                GridItem(.flexible())]
    
var location: DDGLocation

init(location: DDGLocation) {
    self.location = location
}

이때 location은 Initializing을 해줘야한다.

1
@ObservedObject var viewModel: LocationDetailViewModel

그리고 LocationDetailView에서 viewModel을 만들어주고 이전에 사용했떤 변수가 사라졌으니 관련 에러가 나므로 viewModel을 앞에 붙여주자.

길 찾기 기능

MKMapItem을 사용한다 Docs는 여기에

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// viewModel
func getDirectionsToLocation() {
    let placemark = MKPlacemark(coordinate: location.location.coordinate)
    let mapItem = MKMapItem(placemark: placemark)
    mapItem.name = location.name
    
    mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeWalking])
    
}

// LocationDetailView
Button {
    viewModel.getDirectionsToLocation() // new
} label: {
    LocationActionButton(color: .brandPrimary, imageName: "location.fill")
}

여기서 우리는 길안내를 할때 걸어다니는 설정으로 MKLaunchOptionsDirectionsModeWalking을 해주었다.

이제 실행을 해보면?

Image

이렇게 Map 앱이 새로 실행이 되면서 길안내를 해준다.

전화 기능

1
2
3
4
func callLocation() {
    guard let url = URL(string: "tel://\(location.phoneNumber)") else { return }
    UIApplication.shared.open(url)
}

이때 url앞에 tel://을 붙여준다.

시뮬레이터에서는 실제로 기능이 작동하지 않으니 실제 디바이스에서 해야한다.

이때 전화연결 테스트는 우선 하드코딩으로 번호를 설정하고 그 번호로 테스트 하는걸 추천

Profile Modal

UI 디자인

ModalView를 만들어본다.

코드는 다음과 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
struct ProfileModalView: View {
    var body: some View {
        ZStack {
            VStack {
                Spacer()
                    .frame(height: 60)
                
                Text("Harold")
                    .bold()
                    .font(.title2)
                    .lineLimit(1)
                    .minimumScaleFactor(0.75)
                
                Text("Test Company")
                    .fontWeight(.semibold)
                    .lineLimit(1)
                    .minimumScaleFactor(0.75)
                    .foregroundStyle(.secondary)
                
                Text("This is my sample bio. Let's keep typing to see how long we can make this, how does the padding look.")
                    .lineLimit(3)
                    .padding()
            }
            .frame(width: 300,height: 230)
            .background(Color(.secondarySystemBackground))
            .cornerRadius(16)
            .overlay (
                Button {
                    // dismiss
                    
                } label: {
                    XDismissButton()
                }, alignment: .topTrailing
            )
            
            Image(uiImage: PlaceholderImage.avatar)
                .resizable()
                .scaledToFill()
                .frame(width: 110, height: 110)
                .clipShape(Circle())
                .shadow(color: .black.opacity(0.5), radius: 4, x: 0, y: 6)
                .offset(y: -120)
            
        }
    }
}

크게 언급할만한 내용은 없다.

실행하면 다음과 같이 나온다.

Image

Logic & Animation

우선 MockData를 하나 만들어준다.

이전에 만들어둔 CKRecord 의 location을 그대로 복사하여 다음과 같이 수정해주었다.

1
2
3
4
5
6
7
8
9
10
// 여기서 RecordType을 profile로 해준다.
static var profile: CKRecord {
    let record = CKRecord(recordType: RecordType.profile)
    record[DDGProfile.kFirstName]           = "Test"
    record[DDGProfile.kLastName]        = "User"
    record[DDGProfile.kCompanyName]    = "Best Company Ever"
    record[DDGProfile.kBio]     = "This is my bio, I hope it's not too long I can't check character count"
    
    return record
}

이제 위의 UI디자인에서 하드코딩을 했던것을 MockData를 사용하여 적용을 한다.

1
2
3
4
5
Text(profile.firstName + " " + profile.lastName)
// 생략
Text(profile.companyName)
// 생략
Text(profile.bio)

이제 애니메이션을 적용한다.

우선 Modal이 나오게끔 설정을 하기위해 ViewModel에 변수를 하나 만들어준다. @Published var isShowingProfileModal = false

그리고 LocationDetailView에서

1
2
3
4
5
6
7
8
9
LazyVGrid(columns: viewModel.columns) {
    FirstNameAvatarView(
        image: PlaceholderImage.avatar,
        firstName: "Sean"
    )
    .onTapGesture { // new
        viewModel.isShowingProfileModal = true
    }
}

이렇게 탭을 했을때 True로 하여 값이 변경된것을 알려준다.

그리고 화면 위에 띄울것이므로 기존의 Vstack위에 ZStack을 씌워준다.

if를 통해 true일때 나오게 한다

1
2
3
4
5
6
7
8
9
10
11
12
13
if viewModel.isShowingProfileModal {
    Color(.systemBackground)
        .ignoresSafeArea()
        .opacity(0.9)
        .transition(.opacity)
        .animation(.easeOut) // Deprecated Now
        .zIndex(1)
    
    ProfileModalView(isShowingProfileModal: $viewModel.isShowingProfileModal, profile: DDGProfile(record: MockData.profile))
        .transition(.opacity.combined(with: .slide))
        .animation(.easeOut) // Deprecated Now
        .zIndex(2)
}

그리고 다시 ModalView로 돌아와서

1
2
3
4
5
6
7
.overlay (
    Button {
        withAnimation { isShowingProfileModal = false }
    } label: {
        XDismissButton()
    }, alignment: .topTrailing
)

X를 눌렀을때 false를 주어서 사라지게 한다.

Animation이 위의 코드에선 Deprecated 되어있는데 추후 수정 예정

Image

실행하면 위와 같다.


Github: Dub-Dub-Grub Repository

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