diff --git a/Nav.xcodeproj/project.pbxproj b/Nav.xcodeproj/project.pbxproj index dcecd29..d4de00c 100644 --- a/Nav.xcodeproj/project.pbxproj +++ b/Nav.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 26A1500D29083A1300BC7355 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A1500C29083A1300BC7355 /* ContentView.swift */; }; 26A1500F29083A1400BC7355 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 26A1500E29083A1400BC7355 /* Assets.xcassets */; }; 26A1501929083B7B00BC7355 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A1501829083B7B00BC7355 /* MapView.swift */; }; + 26A82ED82A29CCDE00C23407 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A82ED72A29CCDE00C23407 /* LocationManager.swift */; }; CE2D8D3829C59D5F00E5C104 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D8D3729C59D5F00E5C104 /* ImagePicker.swift */; }; CE2D8D3A29C59DF100E5C104 /* CategoryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D8D3929C59DF100E5C104 /* CategoryPicker.swift */; }; DCE7EBCF2A172C1E00644745 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7EBCE2A172C1E00644745 /* Extensions.swift */; }; @@ -37,6 +38,7 @@ 26A1500C29083A1300BC7355 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 26A1500E29083A1400BC7355 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26A1501829083B7B00BC7355 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; + 26A82ED72A29CCDE00C23407 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; CE2D8D3729C59D5F00E5C104 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; CE2D8D3929C59DF100E5C104 /* CategoryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPicker.swift; sourceTree = ""; }; DCE7EBCE2A172C1E00644745 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; @@ -92,6 +94,7 @@ 26A1501029083A1400BC7355 /* Preview Content */, CE2D8D3729C59D5F00E5C104 /* ImagePicker.swift */, CE2D8D3929C59DF100E5C104 /* CategoryPicker.swift */, + 26A82ED72A29CCDE00C23407 /* LocationManager.swift */, ); path = Nav; sourceTree = ""; @@ -227,6 +230,7 @@ CE2D8D3829C59D5F00E5C104 /* ImagePicker.swift in Sources */, 26A1500D29083A1300BC7355 /* ContentView.swift in Sources */, 262C17C2290D090C00450E54 /* Font+.swift in Sources */, + 26A82ED82A29CCDE00C23407 /* LocationManager.swift in Sources */, 262C17C6290E385E00450E54 /* Text+.swift in Sources */, 262C17C0290D06F700450E54 /* Color+.swift in Sources */, FDFD7A6129289926001BE945 /* Bundle+Ext.swift in Sources */, diff --git a/Nav/LocationManager.swift b/Nav/LocationManager.swift new file mode 100644 index 0000000..45046d4 --- /dev/null +++ b/Nav/LocationManager.swift @@ -0,0 +1,43 @@ +// +// LocationManager.swift +// Nav +// +// Created by 김민택 on 2023/06/02. +// + +import Combine +import Foundation +import MapKit +import SwiftUI + +class LocationManager: NSObject, ObservableObject, MKLocalSearchCompleterDelegate { + @Published var searchQuery = "" + var completer: MKLocalSearchCompleter + @Published var completions: [MKLocalSearchCompletion] = [] + var cancellable: AnyCancellable? + + override init() { + completer = MKLocalSearchCompleter() + super.init() + cancellable = $searchQuery.assign(to: \.queryFragment, on: self.completer) + completer.delegate = self + } + + func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { + self.completions = completer.results + } + + func loadAddressCoordinate(_ address: MKLocalSearchCompletion, completoinHandler: @escaping (MKCoordinateRegion) -> Void) { + let searchRequest = MKLocalSearch.Request(completion: address) + let search = MKLocalSearch(request: searchRequest) + search.start { response, error in + guard error == nil else { return } + guard let coordinate = response?.boundingRegion.center else { return } + guard let span = response?.boundingRegion.span else { return } + + completoinHandler(MKCoordinateRegion(center: coordinate, span: span)) + } + } +} + +extension MKLocalSearchCompletion: Identifiable {} diff --git a/Nav/View/MapView.swift b/Nav/View/MapView.swift index e4327d8..bbc7493 100644 --- a/Nav/View/MapView.swift +++ b/Nav/View/MapView.swift @@ -7,126 +7,67 @@ import MapKit import SwiftUI struct MapView: View { - private var mockDatas: [MockDatum] = MockDatum.allData - - @State var searchQueryString = "" + @StateObject var locationManager = LocationManager() // 서울 좌표 @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.5666791, longitude: 126.9782914), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)) - @State private var isLoggedIn: Bool = true - @State private var isClickedYes: Bool = false - @State private var isShowModal: Bool = false + + private var mockDatas: [MockDatum] = MockDatum.allData var body: some View { - NavigationView { + NavigationStack { ZStack { Map(coordinateRegion: $region, showsUserLocation: false, - annotationItems: mockDatas){ + annotationItems: mockDatas) { data in MapMarker(coordinate: data.coordinate) } - HStack { - Spacer() - - VStack { - Button(action: {}) { - Image(systemName: "gearshape") - .circleButton( - iconColor: .navWhite, - iconWidth: 20, - iconHeight: 20, - buttonColor: .primaryRed, - buttonSize: 50, - shadowRadius: 4, - shadowY: 4 - ) - .shadow(radius: 4, y: 4) - } - - NavigationLink { - ListView() - } label: { - Image(systemName: "list.bullet") - .circleButton( - iconColor: .navWhite, - iconWidth: 20, - iconHeight: 14.5, - buttonColor: .primaryRed, - buttonSize: 50, - shadowRadius: 4, - shadowY: 4 - ) - .shadow(radius: 4, y: 4) - } - - Spacer() - - Button { - if isLoggedIn { - isShowModal = true - } else { - isClickedYes = true - } - } label: { - Image(systemName: "plus") - .circleButton( - iconColor: .navWhite, - iconWidth: 17, - iconHeight: 16, - buttonColor: .primaryRed, - buttonSize: 50, - shadowRadius: 4, - shadowY: 4 - ) - .shadow(radius: 4, y: 4) - } - } - .padding(.trailing, 16) - .alert("로그인이 필요한 서비스입니다.", - isPresented: $isClickedYes, - actions: { - Button("취소", role: .cancel, action: {}) - Button("로그인", role: .none, action: {}) - }, - message: { - Text("로그인을 하시면 모든 서비스를 이용하실 수 있습니다. 로그인 하시겠습니까?") - } - ) - .sheet(isPresented: $isShowModal) { - NavigationView { - VStack { - PinCreationView() - } - .padding(16) - .navigationTitle("핀 추가") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button { - isShowModal = false - } label: { - Image(systemName: "xmark") - .foregroundColor(.black) - } - } - } - } - } + if locationManager.searchQuery != "" { + SearchedView(region: $region) + .environmentObject(locationManager) } } .navigationTitle("NAV") .searchable( - text: $searchQueryString, + text: $locationManager.searchQuery, placement: .navigationBarDrawer, prompt: "검색" ) } } - } + +private struct SearchedView: View { + @EnvironmentObject var locationManager: LocationManager + @Binding var region: MKCoordinateRegion + @Environment(\.dismissSearch) private var dismissSearch + + var body: some View { + List(locationManager.completions) { completion in + Button { + locationManager.loadAddressCoordinate(completion) { location in + withAnimation { + region = location + } + } + dismissSearch() + } label: { + VStack(alignment: .leading) { + Text(completion.title) + if completion.subtitle != "" { + Text(completion.subtitle) + .font(.subheadline) + .foregroundColor(.gray) + } + } + } + } + } +} + struct MapView_Previews: PreviewProvider { static var previews: some View { MapView() } -} \ No newline at end of file +}