Learn Courses My Dashboard

MKLocalSearch produces SearchAttribution Error and Error Domain=GEOErrorDomain Code=-8

Hi,
I am trying to use mapkit auto completion in my SwiftUI project and whenever I run the view, I get a bunch of errors such as:

2022-02-12 19:45:59.095462-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.timeout
2022-02-12 19:45:59.095595-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.theculturetrip
2022-02-12 19:45:59.095822-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.redtri
2022-02-12 19:45:59.095909-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.fotospot
2022-02-12 19:45:59.096549-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.timeout from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.timeout" UserInfo={NSDebugDescription=No matching attribution source found for com.timeout}
2022-02-12 19:45:59.096679-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.theculturetrip from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.theculturetrip" UserInfo={NSDebugDescription=No matching attribution source found for com.theculturetrip}
2022-02-12 19:45:59.096761-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.redtri from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.redtri" UserInfo={NSDebugDescription=No matching attribution source found for com.redtri}
2022-02-12 19:45:59.096837-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.fotospot from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.fotospot" UserInfo={NSDebugDescription=No matching attribution source found for com.fotospot}

and then the auto complete breaks until I dismiss and reload the view. Here is my code:

MapAutoComplete.swift
import Foundation
import SwiftUI
import Combine
import MapKit

class MapSearch : NSObject, ObservableObject {
    @Published var locationResults : [MKLocalSearchCompletion] = []
    @Published var searchTerm = ""
    
    private var cancellables : Set<AnyCancellable> = []
    
    private var searchCompleter = MKLocalSearchCompleter()
    private var currentPromise : ((Result<[MKLocalSearchCompletion], Error>) -> Void)?

    override init() {
        super.init()
        searchCompleter.delegate = self
        searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address])
        
        $searchTerm
            .debounce(for: .seconds(0.2), scheduler: RunLoop.main)
            .removeDuplicates()
            .flatMap({ (currentSearchTerm) in
                self.searchTermToResults(searchTerm: currentSearchTerm)
            })
            .sink(receiveCompletion: { (completion) in
                //handle error
            }, receiveValue: { (results) in
                self.locationResults = results.filter { $0.subtitle.contains("United States") } // This parses the subtitle to show only results that have United States as the country. You could change this text to be Germany or Brazil and only show results from those countries.
            })
//            .store(in: &cancellables)
    }
    
    func searchTermToResults(searchTerm: String) -> Future<[MKLocalSearchCompletion], Error> {
        Future { promise in
            self.searchCompleter.queryFragment = searchTerm
            self.currentPromise = promise
        }
    }
}

extension MapSearch : MKLocalSearchCompleterDelegate {
    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
            currentPromise?(.success(completer.results))
        }
    
    func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
        //could deal with the error here, but beware that it will finish the Combine publisher stream
        //currentPromise?(.failure(error))
    }
}

struct ReversedGeoLocation {
    let streetNumber: String    // eg. 1
    let streetName: String      // eg. Infinite Loop
    let city: String            // eg. Cupertino
    let state: String           // eg. CA
    let zipCode: String         // eg. 95014
    let country: String         // eg. United States
    let isoCountryCode: String  // eg. US

    var formattedAddress: String {
        return """
        \(streetNumber) \(streetName),
        \(city), \(state) \(zipCode)
        \(country)
        """
    }

    // Handle optionals as needed
    init(with placemark: CLPlacemark) {
        self.streetName     = placemark.thoroughfare ?? ""
        self.streetNumber   = placemark.subThoroughfare ?? ""
        self.city           = placemark.locality ?? ""
        self.state          = placemark.administrativeArea ?? ""
        self.zipCode        = placemark.postalCode ?? ""
        self.country        = placemark.country ?? ""
        self.isoCountryCode = placemark.isoCountryCode ?? ""
    }
}
struct SearchBar: UIViewRepresentable {

    @Binding var text: String

    class Coordinator: NSObject, UISearchBarDelegate {
        
        @Binding var text: String
        
        init(text: Binding<String>) {
            _text = text
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
        }
    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle = .minimal
        searchBar.becomeFirstResponder()
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}
class LocationSearchService: 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
    }
}

extension MKLocalSearchCompletion: Identifiable {}
struct searchView: View {
    @ObservedObject var locationSearchService: LocationSearchService
    @Environment(\.presentationMode) var presentationMode
    static var address = ""
    var body: some View {
        VStack {
            SearchBar(text: $locationSearchService.searchQuery)
            List(locationSearchService.completions) { completion in
                VStack(alignment: .leading) {
                    Text(completion.title)
                    Text(completion.subtitle)
                        .font(.subheadline)
                        .foregroundColor(.gray)
                }
                .onTapGesture {
                    searchView.address = completion.subtitle
                    presentationMode.wrappedValue.dismiss()
                }
            }
        }
    }
}

I am in desperate need of help, thx in advance!

@Mcrich23

I am assuming that all the code you have posted is not in one file? If that is the case can you edit the post and put breaks in to indicate what belongs where just so that it is easier to decipher.

Add the 3 backticks ``` where you need to break up each code block and provide a file name that you used. For example:

FileName1

Code here

Filename2

Code here

etc
etc

They are all in one file, do you think I should split it?

Yes you should so that the compiler can deal with it a bit easier, but for the time being I’ll see if I can figure out what’s going on.

I Split up the 1 file into 3, and updated my post.

1 Like

Seems to be less code that was the case previously. Are you sure that all the necessary code is there to be able to carry out some testing?

1 Like

Yes, all the code there is just the auto complete and it’s ui, non of the extra ui fluff. Thank you so much!

OK so what does all that code do?
What is the context in which it is used?

If you have this project on GitHub, that may make it easier to get something running to see what’s going on.

Here is a video showing what happens:
https://drive.google.com/file/d/1C418UmusQCfSY7_VRv74ygR5Pf_uJ1hi/view?usp=sharing

However, I get the errors and then the auto complete stops working.

Are you referring to Auto Complete as in the MapAutoComplete stops working or are you referring to Xcode Auto Complete?

The auto complete in the MapAutoComplete stops working.

Have you set breakpoints to examine what is happening in your code as compared to what you expected would happen?

I have not, but will try right now.

Realistically it is impossible for me to work out what is wrong without the rest of the code base so that I can observe what is happening.

Where would you suggest putting breakpoints?

Here is my other related code:

MapView.swift
struct MapView: View {
    static var shared = MapView()
    @State var pinLatitude: Double = 47.6062
    @State var pinLongitude: Double = -122.3321
    @State var loadLatitude: Double = 47.6062
    @State var loadLongitude: Double = -122.3321
    @State var zoom: Double = 0.2
    @State var searchLocation = ""
    @State var noMapLoc = false
    @State var currentLoc = false
    @Environment(\.presentationMode) var presentationMode
    @State private var locManager = CLLocationManager() {
        didSet {
            if locManager.authorizationStatus == .authorizedWhenInUse || locManager.authorizationStatus == .authorizedAlways {
                searchLocation = "Current Location"
            }else {
                allowLocation = true
            }
        }
    }
    @State var reloadMap = false {
        didSet {
            DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1)) {
                reloadMap = false
            }
        }
    }
    @State var shouldOpenPlacePicker = false
    @State var allowLocation = false
    var body: some View {
        GeometryReader { geo in
            VStack {
                Text("Drop a pin to select your location")
                    .font(.headline)
                    .padding()
                HStack {
                    Button("Cancel") {
                        noMapLoc = true
                        presentationMode.wrappedValue.dismiss()
                    }
                    .foregroundColor(.red)
                    .padding(.trailing)
                    Button("Done") {
                        print("pinCoordinate = lat: \(pinLatitude), long: \(pinLongitude)")
                        noMapLoc = false
                        presentationMode.wrappedValue.dismiss()
                    }
                    .padding(.leading)
                }
                if !reloadMap {
                    RawMapView(lat: loadLatitude, long: loadLongitude, zoom: zoom)
                        .overlay(alignment: .topTrailing, {
                            HStack {
                                Button(action: {shouldOpenPlacePicker = true}) {
                                    Image(systemName: "magnifyingglass")
                                        .foregroundColor(.primary)
                                }
                                if Utilities.currentLocationEnabled {
                                    Button(action: {
                                            locManager.requestWhenInUseAuthorization()
                                        print("locManager authStatus = \(locManager.authorizationStatus)")
                                            if locManager.authorizationStatus == .denied {
                                                currentLoc = false
                                                allowLocation = true
                                            }else {
                                                let coordinates = locManager.location?.coordinate
                                                if Utilities.appType == .Debug {
                                                    if coordinates?.latitude == nil {
                                                        loadLatitude = 47.6062
                                                    }else {
                                                        loadLatitude = coordinates!.latitude
                                                    }
                                                    if coordinates?.longitude == nil {
                                                        loadLongitude = -122.3321
                                                    }else {
                                                        loadLongitude = coordinates!.longitude
                                                    }
                                                }else {
                                                    loadLatitude = coordinates!.latitude
                                                    loadLongitude = coordinates!.longitude
                                                }
                                                zoom = 0.05
                                                reloadMap = true
                                            }
    //                                        if locManager.authorizationStatus == .authorizedWhenInUse || locManager.authorizationStatus == .authorizedAlways {
    //                                        }else {
    //                                            allowLocation = true
    //                                        }
                                    }, label: {
//                                        if (locManager.authorizationStatus == .authorizedWhenInUse || locManager.authorizationStatus == .authorizedAlways) {
//                                            if loadLatitude == 47.6062 && loadLongitude == -122.3321 {
//                                                Image(systemName: "location.fill")
//                                            }else if loadLatitude == locManager.location?.coordinate.latitude && loadLongitude == locManager.location?.coordinate.longitude {
//                                                Image(systemName: "location.fill")
//                                            }else {
//                                                Image(systemName: "location")
//                                            }
//                                        }else {
                                            Image(systemName: "location")
//                                        }
                                    })
                                }
//                                Text("Enter Location")
                            }
                            .sheet(isPresented: $shouldOpenPlacePicker, onDismiss: {
//                                MapView.pinLatitude =
                                let address = searchView.address

                                   let geoCoder = CLGeocoder()
                                   geoCoder.geocodeAddressString(address) { (placemarks, error) in
                                       guard
                                           let placemarks = placemarks,
                                           let location = placemarks.first?.location
                                       else {
                                           // handle no location found
                                           return
                                       }
                                       loadLatitude = location.coordinate.latitude
                                       loadLongitude = location.coordinate.longitude
                                       zoom = 0.1
                                       reloadMap = true
                                       // Use your location
                                   }
                            }, content: {searchView(locationSearchService: LocationSearchService())})
                            .descreteOpaqueBackground(color: .white, diameter: 2)
                            .padding()
                        })
                }else {
//                    Loading(selectedType: .semiCircleSpin, duration: 0.0)
                }
            }
            .ignoresSafeArea()
            .alert(isPresented: $allowLocation, content: {
                Alert(title: Text("Uh Oh"), message: Text("We're not able to find your location, please open settings to enable it."), primaryButton: .destructive(Text("Cancel"), action: {
                    allowLocation = false
                }), secondaryButton: .default(Text("Open Settings"), action: {
                    allowLocation = false
                    if let appSettings = URL(string: UIApplication.openSettingsURLString) {
                        UIApplication.shared.open(appSettings)
                    }
                }))
            })
        }
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}
RawMapView.swift
struct RawMapView: UIViewRepresentable {
    var lat: Double
    var long: Double
    var zoom: Double
        func updateUIView(_ mapView: MKMapView, context: Context) {

            let span = MKCoordinateSpan(latitudeDelta: zoom, longitudeDelta: zoom)
            var chicagoCoordinate = CLLocationCoordinate2D()
            chicagoCoordinate.latitude = lat
            chicagoCoordinate.longitude = long
            let region = MKCoordinateRegion(center: chicagoCoordinate, span: span)
            mapView.setRegion(region, animated: true)
        }
    

        func makeUIView(context: Context) -> MKMapView {

            let myMap = MKMapView(frame: .zero)
            let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(RawMapViewCoordinator.addAnnotation(gesture:)))
            longPress.minimumPressDuration = 0.5
            myMap.addSwipeGestureRecognizer(for: [.down, .left, .right, .up], target: myMap, action: #selector(RawMapViewCoordinator.updateCoordinates(gesture:)))
            myMap.addGestureRecognizer(longPress)
            myMap.delegate = context.coordinator
            return myMap

        }

    func makeCoordinator() -> RawMapViewCoordinator {
        return RawMapViewCoordinator(self)
    }

    class RawMapViewCoordinator: NSObject, MKMapViewDelegate {
        var entireMapViewController: RawMapView

        init(_ control: RawMapView) {
          self.entireMapViewController = control
        }
        
        @objc func updateCoordinates(gesture: UIGestureRecognizer) {
            print("get map coordinates")
            if let mapView = gesture.view as? MKMapView {
                print("Map Coordinates = \(mapView.centerCoordinate.latitude), \(mapView.centerCoordinate.longitude)")
                MapView.shared.loadLatitude = mapView.centerCoordinate.latitude
                MapView.shared.loadLongitude = mapView.centerCoordinate.longitude
            }
        }

        @objc func addAnnotation(gesture: UIGestureRecognizer) {

            if gesture.state == .began {
                
                if let mapView = gesture.view as? MKMapView {
                    for point in mapView.annotations {
                        mapView.removeAnnotation(point)
                    }
                    let point = gesture.location(in: mapView)
                    let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
                    let annotation = MKPointAnnotation()
                    annotation.coordinate = coordinate
                    mapView.addAnnotation(annotation)
                    print("pinCoordinate = lat: \(coordinate.longitude), long: \(coordinate.longitude)")
                    MapView.shared.pinLatitude = coordinate.latitude
                    MapView.shared.pinLongitude = coordinate.longitude
                }
            }
        }
    }
}

@Mcrich23

Morris,

I am still getting errors. Can you provide the Utilities.swift file and the file containing the definition of .descreteOpaqueBackground
OR paste in the definition and I’ll set up something so that .descreteOpaqueBackground works.

Here you go:

Utilities.swift
class Utilities {
    static var code = String()
    static var shareMode: shareTypes = .sheet
    static let buildString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
    static let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
    static let build = Int(buildString!)
    static let version = Double(versionString!)
    static var openNum = UserDefaults.standard.integer(forKey: "openNum")
    static var name = UserDefaults.standard.string(forKey: "deviceName") ?? ""
    static var joinDisabled = false
    static var location = String()
    static var latLong = false
    static var currentLoc = false
    static var mapLoc = false
    static var cuisine = String()
    static var restFilter = String()
    static var mediaType: mediaTypeOpt = .TVShows
    static var price = String()
    static var appType = AppConfiguration.AppStore
    static var switchEnabled:Bool = Utilities.remoteConfig["switchEnabled"].boolValue
    static var welcomePopup = UserDefaults.standard.bool(forKey: "welcomePopup")
    static let noImage = "https://st4.depositphotos.com/14953852/22772/v/600/depositphotos_227725020-stock-illustration-image-available-icon-flat-vector.jpg"
    static var topPick = "None"
    static let currentLocationEnabled = true
    static let shareEnabled = false
    static var ignoreUpdate = false
    static var restMock: [Restaurants] = []
    static var mediaMock: [Media] = []
    static func encodeForGoogleMaps(url: String, completion: @escaping (String) -> Void) {
        let Url = url.replacingOccurrences(of: "|", with: "%7C").replacingOccurrences(of: ",", with: "%2C").replacingOccurrences(of: " ", with: "%20").replacingOccurrences(of: "'", with: "%27")
        completion(Url)
    }
    static var picktType = picktTypeOpt.restaurants {
        didSet {
            UserDefaults.standard.set(picktType.rawValue, forKey: "picktType")
        }
    }
    enum picktTypeOpt: String, CaseIterable {
        case restaurants = "restaurants"
        case media = "media"
    }
    enum mediaTypeOpt: String, CaseIterable {
        case TVShows = "TV Shows"
        case Movies = "Movies"
    }
    static var remoteConfig: RemoteConfig!
    static func presentShareSheet(activityItems: [Any]) {
        let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
        guard var topVC = UIApplication.shared.windows.first?.rootViewController else {
            return
        }
        // iterate til we find the topmost presented view controller
        // if you don't you'll get an error since you can't present 2 vcs from the same level
        while let presentedVC = topVC.presentedViewController {
            topVC = presentedVC
        }
        
        topVC.present(activityController, animated: true)
    }
    static func convertNSNumberToDouble(number: NSNumber, completion: @escaping (_ Double: Double) -> Void) {
        var numberDouble: Double

        numberDouble = number as! Double // 1
        numberDouble = Double(exactly: number)! // 2

        numberDouble = Double(truncating: number) // 3
        numberDouble = number.doubleValue // 4

        if let x = number as? Double { // 5
            numberDouble = x
        }

        if let x = Double(exactly: number) { // 6
            numberDouble = x
        }
        completion(numberDouble)
    }
    static func showFinalRestView(completion: @escaping () -> Void) {
        @ObservedObject var viewModels = firebase()
        viewModels.fetchData() {
            Devices.correctList()
            completion()
            print("yesRestaurants = \(viewModels.yesRestaurants), allRestaurants = \(viewModels.allRestaurants)")
        }
    }
    static func getShareMode() {
        let mode = Utilities.remoteConfig["shareMode"].stringValue
        if mode == shareTypes.sheet.rawValue {
            shareMode = .sheet
        }else {
            shareMode = .clipboard
        }
    }
    static func fetchRemoteConfig() {
        print("refreshing")
        remoteConfig = RemoteConfig.remoteConfig()
        let settings = RemoteConfigSettings()
        if Utilities.appType == .Debug {
            settings.minimumFetchInterval = 0
        }else {
            settings.minimumFetchInterval = 43200 //12 hours
        }
        print("min fetch interval = \(settings.minimumFetchInterval)")
        remoteConfig.configSettings = settings
        remoteConfig.setDefaults(fromPlist: "RemoteConfigDefaults")
        remoteConfig.fetchAndActivate { status, error in
            if status == .successFetchedFromRemote {
                print("fetched from remote, \(remoteConfig)")
            }else if status == .successUsingPreFetchedData {
                print("fetched locally, \(remoteConfig)")
            }else if status == .error {
                print("error fetching = \(error)")
            }
        }
        print("remote config = \(remoteConfig)")
    }
    static func checkForUpdate(completion: @escaping (Bool) -> Void) {
        let db = Firestore.firestore()
        print("app type = \(Utilities.appType)")
        if Utilities.appType == .AppStore {
            db.collection("Utilities").document("AppStore Build").addSnapshotListener { document, error in
                if error == nil {
                    if document != nil && document!.exists {
                        if let version = document!.get("Version") as? Double {
                            if Utilities.version! < version {
                                print("updateApp")
                                completion(true)
                            }else {
                                completion(false)
                            }
                        }else {
                            completion(false)
                        }
                    }else {
                        completion(false)
                    }
                }else {
                    completion(false)
                }
            }
        }else {
            db.collection("Utilities").document("TestFlight Build").addSnapshotListener { document, error in
                if error == nil {
                    if document != nil && document!.exists {
                        if let version = document!.get("Version") as? Double {
                            print("version = \(version)")
                            if Utilities.version! < version {
                                print("updateApp")
                                completion(true)
                            }else {
                                if let build = document!.get("Build") as? Int {
                                    print("build = \(build)")
                                    if Utilities.build! < build {
                                        print("updateApp")
                                        completion(true)
                                    }else {
                                        completion(false)
                                    }
                                }else {
                                    completion(false)
                                }
                            }
                        }else {
                            completion(false)
                        }
                    }else {
                        completion(false)
                    }
                }else {
                    completion(false)
                }
            }
        }
    }
    enum AppConfiguration {
      case Debug
      case TestFlight
      case AppStore
    }

    struct Config {
      // This is private because the use of 'appConfiguration' is preferred.
      private static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
      
      // This can be used to add debug statements.
      static var isDebug: Bool {
        #if DEBUG
          return true
        #else
          return false
        #endif
      }

      static var appConfiguration: AppConfiguration {
        if isDebug {
          return .Debug
        }else if isTestFlight {
          return .TestFlight
        }else {
          return .AppStore
        }
      }
    }
    static func getAppType() {
      switch (Config.appConfiguration) {
      case .Debug:
          Utilities.appType = .Debug
        break
      case .TestFlight:
          Utilities.appType = .TestFlight
        break
      default:
          Utilities.appType = .AppStore
        break
      }
        print("App Type: \(Utilities.appType)")
    }
}
class NilJson: ObservableObject {
    let nilJson: [Restaurants]

    var primary: Restaurants

    init() {
        let url = Bundle.main.url(forResource: "locations", withExtension: "json")!
        let data = try! Data(contentsOf: url)
        nilJson = try! JSONDecoder().decode([Restaurants].self, from: data)
        primary = nilJson[Int.random(in: 0..<nilJson.count)]
    }
}

extension AnyTransition {
    static var moveAndFade: AnyTransition {
        .asymmetric(
            insertion: .move(edge: .trailing).combined(with: .opacity),
            removal: .scale.combined(with: .opacity)
        )
    }
}

struct opaqueBack: ViewModifier {
    @State var color: Color
    @State var diameter: Double
    func body(content: Content) -> some View {
        if diameter != -1 {
            content
                .padding(diameter)
                .background(color)
                .cornerRadius(10)
                .opacity(0.8)
        }else {
            content
                .padding()
                .background(color)
                .cornerRadius(10)
                .opacity(0.8)
        }
    }
}
extension View {
    func descreteOpaqueBackground(color: Color, diameter: Double) -> some View {
        modifier(opaqueBack(color: color, diameter: diameter))
    }
    func autoOpaqueBackground(color: Color) -> some View {
        modifier(opaqueBack(color: color, diameter: -1))
    }
}
struct DraggableCardView<Content: View>: View {

    let content: Content
    let topPadding: CGFloat
    let fixedHeight: Bool
    let bgColor: Color

    init(topPadding: CGFloat = 100, fixedHeight: Bool = false, bgColor: Color = .white, @ViewBuilder content: () -> Content) {
        self.content = content()
        self.topPadding = topPadding
        self.fixedHeight = fixedHeight
        self.bgColor = bgColor
    }

    var body: some View {
        ZStack {
            bgColor.cornerRadius(40, corners: [.topLeft, .topRight])
            VStack {
                Color.white
                    .frame(width: 72, height: 6)
                    .clipShape(Capsule())
                    .padding(.top, 15)
                    .padding(.bottom, 10)

                content
                    .padding(.bottom, 30)
                    .applyIf(fixedHeight) {
                        $0.frame(height: UIScreen.main.bounds.height - topPadding)
                    }
                    .applyIf(!fixedHeight) {
                        $0.frame(maxHeight: UIScreen.main.bounds.height - topPadding)
                    }
            }
        }
        .fixedSize(horizontal: false, vertical: true)
    }
}
extension Color {
    init(hex: String) {
        let scanner = Scanner(string: hex)
        var rgbValue: UInt64 = 0
        scanner.scanHexInt64(&rgbValue)

        let r = (rgbValue & 0xff0000) >> 16
        let g = (rgbValue & 0xff00) >> 8
        let b = rgbValue & 0xff

        self.init(red: Double(r) / 0xff, green: Double(g) / 0xff, blue: Double(b) / 0xff)
    }
}

extension View {

    @ViewBuilder
    func applyIf<T: View>(_ condition: Bool, apply: (Self) -> T) -> some View {
        if condition {
            apply(self)
        } else {
            self
        }
    }

    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}
enum shareTypes: String, CaseIterable {
    case clipboard = "clipboard"
    case sheet = "sheet"
}

Thank you again!

@Mcrich23

Morris,

Unfortunately this just gets more complex with more files and definitions missing including the model for Restaurants and Media, RemoteConfig…

Do you have this project on GitHub? Might be a whole lot easier. Either that or compress the entire project and post to Dropbox and then create a share link and post in a reply OR send me a private message with the DropBox share link.

If you use Google Drive then make sure that the share link does not require some kind of authentication on my part as I do not provide email addresses.

Cheers

Absolutely. However, my GitHub is private because I don’t want people copying my code when I eventually launch my app. Can I please have your GitHub username to add you as a collaborator?