Swiftui with delegate functions for mkmapview and uitextfield
Asked Answered
T

4

8

I'm practicing and reading up on SwiftUI and I am starting off by making a simple app where you can type a location into a TextField View from swiftUI and then on enter the mkmapview within SwiftUI again will go to that location that I searched for. I can't for the life of me seem to grasp how to get this functionality setup with my mkmapview.

Additionally I can't even find a way to dismiss the keyboard from Textfield without doing a navigation or rendering a new view or removing a view.

Can anyone point to me how I can dismiss my keyboard? I'm used to resignFirstResponder() or didFinishEditing().
Also, does anyone have any suggestions on how to assign functionality to the MKMapView from swiftUI or am I looking at this all wrong?

I figured in my Textfield's onCommit handler I would call a function that could pass necessary data to MapView and handle that but I haven't been able to find a good way to do it.

struct MainView : View {

    @State var text: String = ""

    var body: some View {
        VStack {
            MapView()
                .frame(height: UIScreen.main.bounds.height)
                .offset(y: 50)
                //.edgesIgnoringSafeArea(.top)

            SearchField(text: "")
                .offset(y: -(UIScreen.main.bounds.height) + 60)
                .padding()
        }
    }
}
struct MapView : UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        let coordinate = CLLocationCoordinate2D(
            latitude: 34.011286, longitude: -116.166868)
        let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}
struct SearchField : View {
    @State var text: String
    @State var showDetails: Bool = false

    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 30).frame(width: 300, height: 40)
                .foregroundColor(.white)
                .offset(y: 55)
            TextField($text, placeholder: Text("City, State, or Address"), onEditingChanged: { body in
                print(body)

            }, onCommit: {
                print(self.text)
            })
                .padding()
                .offset(x: 50)
        }
    }
}

*Edit Note: my code above works, it will build a full screen map view with a text field on it and the text field takes text and will update it's state. My question is about moving forward from this with getting functionality to mapview and hiding my damn keyboard on return

Troostite answered 6/6, 2019 at 4:16 Comment(1)
I think you need to get SearchField to bind to the text property in MainView, but I've not managed to get that working yet. EDIT: posted proposed approach below.Ludivinaludlew
T
0

Okay guys,

So from what I have seen so far keyboards will only dismiss on some ui change but I found a pretty elegant solution!

By using a Binding declaration for a boolean "didEnter" which I can pass between views, when the user hit's return on the keyboard the onCommit() function is called within the TextField view. So I added a toggle for onCommit that actually switches my TextField view to a Text view that displays the text simulating the same look, then after some task is completed I toggle didEnter again which switches back to the TextField.

Still feels like a workaround but works for now.

struct InputView : View {
    @Binding var text:          String
    @Binding var didEnter:      Bool
    @State   var searchType:    SearchType

    var body: some View {
        ZStack{
            RoundedRectangle(cornerRadius: 15).frame(width: 310, height: 100)
                .foregroundColor(.secondary)
                .offset(y: -20)
            ZStack{
                RoundedRectangle(cornerRadius: 30).frame(width: 290, height: 40)
                    .foregroundColor(.white)
                if(!didEnter){
                    TextField($text, placeholder: Text("City, State, Address")) {
                        print(self.text)
                        self.didEnter.toggle()
                    }
                        .frame(width: 220, height: 40, alignment: .leading)
                        .offset(x: -20)
                }
                else{
                    Text(self.text).frame(width: 220, height: 40, alignment: .leading)
                        .offset(x: -20)
                }
                Text("Select Location:").bold().fontWeight(.medium)
                    .offset(y: -40)
                    .foregroundColor(.white)
            }
        }
    }
}
Troostite answered 11/6, 2019 at 16:34 Comment(0)
K
3

To dismiss the keyboard when pressing return button in TextField, you can use:

UIApplication.shared.keyWindow?.endEditing(true)

Calling it on TextField's onCommit method:

TextField($searchString, placeholder: Text("Search...")) {
     UIApplication.shared.keyWindow?.endEditing(true)
   }
   .font(.system(size: 23))
   .padding()
Kktp answered 2/7, 2019 at 3:1 Comment(0)
P
0

Use a coordinator. Check out their Landmarks example. Search for delegate in the tutorial. I’m writing on the go so can’t post the link.

I did this for UIImagePickerController

Pare answered 6/6, 2019 at 4:16 Comment(3)
This? developer.apple.com/tutorials/swiftui/interfacing-with-uikitFlattish
You're right, I ended up using a coordinator to get the behavior I wanted with mapkit, makes a lot more sense. Also, I'm convinced we are just at a point where we can't expect SwiftUI to do everything it's advertised to do so simply, still very early.Troostite
@alexbuga, is your code for UIImagePickerController someplace? I can get everything working except it won't present a second time after the first time is dismissed. Any chance you have something that works? (I'm still not convinced it's simply a beta 1 bug.Boethius
L
0

My gut feel is you need to pass the binding into the SearchField like this:

struct MainView : View {

    @State var text: String = ""

    var body: some View {
        VStack {
            MapView()
                .frame(height: UIScreen.main.bounds.height)
                .offset(y: 50)
            //.edgesIgnoringSafeArea(.top)

            SearchField(text: $text)
                .offset(y: -(UIScreen.main.bounds.height) + 60)
                .padding()
        }
    }
}

struct MapView : UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        let coordinate = CLLocationCoordinate2D(
            latitude: 34.011286, longitude: -116.166868)
        let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}

struct SearchField : View {
    @State var text: Binding<String>

    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 30).frame(width: 300, height: 40)
                .foregroundColor(.white)
                .offset(y: 55)
            TextField(text, placeholder: Text("City, State, or Address"), onEditingChanged: { body in
                print(self.text)

            }, onCommit: {
                print(self.text)
            })
                .padding()
                .offset(x: 50)
        }
    }
}
Ludivinaludlew answered 6/6, 2019 at 8:7 Comment(2)
So both ways work, as a binding or the way I implementedTroostite
but this isn't addressing my main issue of not being able to add functionality to mkmapview within swiftui or handling my keyboardTroostite
T
0

Okay guys,

So from what I have seen so far keyboards will only dismiss on some ui change but I found a pretty elegant solution!

By using a Binding declaration for a boolean "didEnter" which I can pass between views, when the user hit's return on the keyboard the onCommit() function is called within the TextField view. So I added a toggle for onCommit that actually switches my TextField view to a Text view that displays the text simulating the same look, then after some task is completed I toggle didEnter again which switches back to the TextField.

Still feels like a workaround but works for now.

struct InputView : View {
    @Binding var text:          String
    @Binding var didEnter:      Bool
    @State   var searchType:    SearchType

    var body: some View {
        ZStack{
            RoundedRectangle(cornerRadius: 15).frame(width: 310, height: 100)
                .foregroundColor(.secondary)
                .offset(y: -20)
            ZStack{
                RoundedRectangle(cornerRadius: 30).frame(width: 290, height: 40)
                    .foregroundColor(.white)
                if(!didEnter){
                    TextField($text, placeholder: Text("City, State, Address")) {
                        print(self.text)
                        self.didEnter.toggle()
                    }
                        .frame(width: 220, height: 40, alignment: .leading)
                        .offset(x: -20)
                }
                else{
                    Text(self.text).frame(width: 220, height: 40, alignment: .leading)
                        .offset(x: -20)
                }
                Text("Select Location:").bold().fontWeight(.medium)
                    .offset(y: -40)
                    .foregroundColor(.white)
            }
        }
    }
}
Troostite answered 11/6, 2019 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.