I would like to show on the screen the distance (Unknown, far, near or right here) and the ID of the found beacon. This is my code
struct ContentView: View{
#ObservedObject var detector = BeaconDetector()
var body: some View {
if detector.lastDistance == .immediate {
return Text("RIGHT HERE")
// id here
.modifier(BigText())
.background(Color.green)
.edgesIgnoringSafeArea(.all)
} else if detector.lastDistance == .near {
return Text("NEAR")
.modifier(BigText())
.background(Color.orange)
.edgesIgnoringSafeArea(.all)
} else if detector.lastDistance == .far {
return Text("FAR")
.modifier(BigText())
.background(Color.red)
.edgesIgnoringSafeArea(.all)
} else {
return Text("UNKNOWN")
.modifier(BigText())
.background(Color.gray)
.edgesIgnoringSafeArea(.all)
}
}
}
import Combine
import CoreLocation
import SwiftUI
class BeaconDetector: NSObject, ObservableObject,
CLLocationManagerDelegate{
var didChange = PassthroughSubject<Void, Never>()
var locationManager: CLLocationManager?
var beaconID = UUID().self
#Published var lastDistance = CLProximity.unknown
override init() {
super.init()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
if CLLocationManager.isMonitoringAvailable(for:
CLBeaconRegion.self){
if CLLocationManager.isRangingAvailable(){
startScanning()
}
}
}
}
func startScanning() {
let uuid = UUID(uuidString: "D3D6736B-4C7C-412D-865B-EE61ACF88C61")!
let constraint = CLBeaconIdentityConstraint(uuid: uuid, major: 123,
minor: 456)
let beconRegion = CLBeaconRegion(beaconIdentityConstraint:
constraint, identifier: "MyBeacon")
locationManager?.startMonitoring(for: beconRegion)
locationManager?.startRangingBeacons(satisfying: constraint)
}
func locationManager(_ manager: CLLocationManager, didRange beacons:
[CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
if let beacon = beacons.first {
update(distance: beacon.proximity)
} else {
update(distance: .unknown)
}
}
func update(distance: CLProximity) {
lastDistance = distance
didChange.send(())
}
}
struct BigText: ViewModifier {
func body(content: Content) -> some View {
content
.font(Font.system(size: 72, design: .rounded))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0,
maxHeight: .infinity)
}
}
Try something like this:
Text("Beacon with major: \(detector.lastMajor) minor: \(detector.lastMinor) is RIGHT HERE")
.modifier(BigText())
.background(Color.green)
.edgesIgnoringSafeArea(.all)
If the BeaconDetector class has a field for lastMajor and lastMinor you can reference these inside your string to be shown on the screen by escaping the expression inside the string with \\().
Since the clarified code shows that the major and minor are not fields on BeaconDetector you will need to add them like this:
#Published var lastMajor: NSNumber? = nil
#Published var lastMinor: NSNumber? = nil
And then you will need to update them when you update the proximity like this:
update(distance: beacon.proximity, major: beacon.major, minor: beacon.minor)
And add these to your update function like this:
func update(distance: CLProximity, major: NSNumber, minor: NSNumber) {
lastDistance = distance
lastMajor = major
lastMinor = minor
didChange.send(())
}
Related
I'm trying to make an AirTag Detector to my Final's Papers in SwiftUI, but I can't find them because they don't have any ID, just his Serial Number and I can't use that. Somehow I can create an app to simulate "Find app" from Apple or find UUID from IT?
If UUID doesn't work, have another way to do that?
I saw in this URL maybe we can't get AirTag's ID.
Is that true?
https://forums.macrumors.com/threads/airtags-bluetooth-id-tracking-explained.2294592/
import Combine
import CoreLocation
import SwiftUI
class BeaconDetector: NSObject, ObservableObject, CLLocationManagerDelegate
{
var didChange = PassthroughSubject<Void, Never>()
var locationManager : CLLocationManager?
var lastDistance = CLProximity.unknown
override init(){
super.init()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
}
func locationManager( _ manager:CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus){
if status == .authorizedWhenInUse {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self){
if CLLocationManager.isRangingAvailable() {
startScanning()
}
}
}
}
func startScanning() {
let uuid = UUID(uuidString: "d31bb872-27f2-11ed-a261-0242ac120002")! //Doesn't exist this UUID
let constraint = CLBeaconIdentityConstraint(uuid: uuid, major: 69, minor: 690)
let beaconRegion = CLBeaconRegion(beaconIdentityConstraint: constraint, identifier: "Home")
locationManager?.startMonitoring(for: beaconRegion)
locationManager?.startRangingBeacons(satisfying: constraint)
}
func locationManager(_ manager: CLLocationManager, didRange beacons:
[CLBeacon], satisfying beaconConstraint:
CLBeaconIdentityConstraint) {
if let beacon = beacons.first {
update(distance: beacon.proximity)
} else {
update(distance: .unknown)
}
}
func update(distance: CLProximity) {
lastDistance = distance
didChange.send(())
}
}
struct BigText: ViewModifier {
func body (content: Content) -> some View {
content
.font(Font.system(size: 72, design: .rounded))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
struct ContentView: View {
#ObservedObject var detector = BeaconDetector()
var body: some View {
if detector.lastDistance == .immediate {
return Text("RIGHT HERE")
.modifier(BigText())
.background(Color.red)
.edgesIgnoringSafeArea(.all)
} else if detector.lastDistance == .near {
return Text("NEAR")
.modifier(BigText())
.background(Color.orange)
.edgesIgnoringSafeArea(.all)
} else if detector.lastDistance == .far {
return Text("FAR")
.modifier(BigText())
.background(Color.blue)
.edgesIgnoringSafeArea(.all)
} else {
return Text("UNKNOWN")
.modifier(BigText())
.background(Color.gray)
.edgesIgnoringSafeArea(.all)
}
}
}
This is actually the same issue as this post AVCapturePhotoCaptureDelegate photoOutput() not called every time however no one responded to that . I find that on takePic the photoOutput function is called sometimes and not others it is literally 50/50 . I am using Swiftui 2.0 . Does anyone know a work around this or why this issue is happening ? The code to replicate this is actually quite small .It is the code below and then setting permissions in the info.plist for Privacy - Camera usage description and privacy - photo library usage description . I have tried different things and it is literally still a 50/50 on whether photoOutput gets called . When it is not called you will see this in log print("Nil on SavePic:picData") Any suggestions would be great .
import SwiftUI
import AVFoundation
struct CreateStoryView: View {
#StateObject var camera = CameraModel()
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack {
// Going to be Camera Preview
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack {
HStack {
Spacer()
Image(systemName: "arrowshape.turn.up.backward.circle.fill")
.foregroundColor(.black)
.padding(.trailing,20)
.background(Color.white)
.clipShape(/*#START_MENU_TOKEN#*/Circle()/*#END_MENU_TOKEN#*/)
.onTapGesture {
if camera.session.isRunning == true {
camera.session.stopRunning()
}
self.presentationMode.wrappedValue.dismiss()
}
if camera.isTaken {
Button(action: camera.reTake, label: { // camera.reTake
Image(systemName: "arrow.triangle.2.circlepath.camera")
.foregroundColor(.black)
.padding()
.background(Color.white)
.clipShape(/*#START_MENU_TOKEN#*/Circle()/*#END_MENU_TOKEN#*/)
})
.padding(.trailing,10)
}
}
Spacer()
HStack{
// If Taken then showing save and again take button
if camera.isTaken{
Button(action: {if !camera.isSaved{camera.savePic()}}, label: {
Text(camera.isSaved ? "Saved" : "Save")
.foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical,10)
.padding(.horizontal,20)
.background(Color.white)
.clipShape(Capsule())
})
.padding(.leading)
Spacer()
} else {
Button(action: camera.takePic , label: {
ZStack{
Circle()
.fill(Color.white)
.frame(width: 65, height: 65)
Circle()
.stroke(Color.white,lineWidth: 2)
.frame(width: 75, height: 75)
}
})
}
}.frame(height: 75)
}
}.onAppear(perform: {
camera.Check()
})
}
}
// Camera Model
class CameraModel: NSObject,ObservableObject,AVCapturePhotoCaptureDelegate {
#Published var isTaken = false
#Published var session = AVCaptureSession()
#Published var alert = false
#Published var output = AVCapturePhotoOutput()
// preview ...
#Published var preview: AVCaptureVideoPreviewLayer!
#Published var isSaved = false
#Published var picData = Data(count: 0)
func Check() {
// first checking camera has permission
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setUp()
return
case .notDetermined:
//retrusting for permission
AVCaptureDevice.requestAccess(for: .video) {(status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp() {
// setting up camera
do{
// setting configs...
self.session.beginConfiguration()
// change for your own
let device = AVCaptureDevice.default(.builtInDualCamera,for: .video,position: .back)
let input = try AVCaptureDeviceInput(device: device!)
// checking and adding session
if self.session.canAddInput(input) {
self.session.addInput(input)
}
// same for output
if (self.session.canAddOutput(self.output)) {
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
} catch {
print(error.localizedDescription)
}
}
// take and retake
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
self.session.stopRunning()
DispatchQueue.main.async {
withAnimation{ self.isTaken.toggle() }
}
}
}
func reTake() {
DispatchQueue.global(qos: .background).async {
self.session.startRunning()
DispatchQueue.main.async {
// withAnimation{ self.isTaken.toggle() }
// clearing
//self.isSaved = false
self.isTaken = false
}
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
print("photoOutput check")
if error != nil {
return
}
guard var imageData = photo.fileDataRepresentation() else {return}
self.picData = imageData
if isSaved == true {
if !imageData.isEmpty {
imageData.removeAll()
isSaved = false
}
} else {
isSaved = true
}
}
func savePic() {
if UIImage(data: self.picData) == nil {
print("Nil on SavePic:picData")
return
}
let image = UIImage(data: self.picData)!
// saving image
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
self.isSaved = true
print("saved sucessfully")
}
#objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
print("Save finished!")
}
}
// setting up view for preview
struct CameraPreview: UIViewRepresentable {
#ObservedObject var camera: CameraModel
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
struct CreateStoryView_Previews: PreviewProvider {
static var previews: some View {
CreateStoryView()
}
}
I have already decode my JSON API and successfully display the location on the map with MapAnnotations and put NavigationLink to see the detail on it. But somehow, when I zoomed out the map to see all marked locations, suddenly my view becomes very laggy with simulator and real iPhone 8 (maybe because I have 100+ annotations on the map?). And then I tried to use MapMarker and the view becomes more smoother, but the problem is now I can't put NavigationLink on MapMarker as well as MapPin. Is there a proper way to display marker/annotations on the map and NavigationLink without making the view lag??
Here is my LocationManager Code to track user's location
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var location: CLLocation?
override init() {
super.init()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
locationManager.stopUpdatingLocation()
DispatchQueue.main.async {
self.location = location
}
}
}
My ContentView to display the Map and Show the annotations
import SwiftUI
import MapKit
import Combine
struct ContentView: View {
var body: some View {
NavigationView{
ServiceLocation()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension MKCoordinateRegion {
static var defaultRegion: MKCoordinateRegion {
MKCoordinateRegion(center: CLLocationCoordinate2D.init(latitude: -0.789275, longitude: 113.921327), latitudinalMeters: 5000, longitudinalMeters: 5000)
}
}
//MARK: MAP VIEW
struct ServiceLocation: View{
#State var serviceLocations: [ServiceLocationJSON] = []
#ObservedObject private var locationManager = LocationManager()
#State private var region = MKCoordinateRegion.defaultRegion
#State private var cancellable: AnyCancellable?
private func setCurrentLocation() {
cancellable = locationManager.$location.sink { location in
region = MKCoordinateRegion(center: location?.coordinate ?? CLLocationCoordinate2D(), latitudinalMeters: 20000, longitudinalMeters: 20000)
}
}
var body: some View{
GeometryReader{ geometry in
VStack{
if locationManager.location != nil {
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: .none, annotationItems: serviceLocations) { location in
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: location.LATITUDE, longitude: location.LONGITUDE)){
NavigationLink(destination: serviceLocationDetail(serviceLocations: location)){
Image(systemName: "mappin")
.resizable()
.scaledToFit()
.frame(width: geometry.size.width / 15, height: geometry.size.height / 15)
}
}
}
} else {
VStack{
Spacer()
ProgressView()
Spacer()
}
}
}.onAppear{
setCurrentLocation()
getServiceLocation(url: "https://my.api.mockaroo.com/latlong.json?key=e57d0e40"){ (serviceLocations) in
self.serviceLocations = serviceLocations
}
}
.navigationTitle("Service")
.navigationBarTitleDisplayMode(.inline)
}
}
}
//MARK: DETAIL VIEW
struct serviceLocationDetail: View{
var serviceLocations: ServiceLocationJSON
var body: some View{
VStack{
if serviceLocations.DEALER_NAME.isEmpty{
VStack{
Spacer()
ProgressView()
Spacer()
}
}else{
VStack(alignment: .leading, spacing: 10){
Text(serviceLocations.DEALER_NAME)
.fontWeight(.medium)
.padding(.leading, 10)
Text(serviceLocations.DEALER_ADDRESS)
.padding(.leading, 10)
HStack(spacing: 5){
Image(systemName: "phone.fill")
Text(serviceLocations.PHONE)
}.padding(.leading, 10)
Spacer()
}.navigationBarTitle(serviceLocations.DEALER_NAME)
}
}
Spacer()
}
}
//MARK: JSON MODEL
struct ServiceLocationJSON: Identifiable, Decodable{
var id: Int
var LATITUDE: Double
var LONGITUDE: Double
var DEALER_NAME: String
var DEALER_ADDRESS: String
var DEALER_PICTURE: String
var PHONE: String
}
//MARK: DECODE JSON MODEL
func getServiceLocation(url: String, completion: #escaping ([ServiceLocationJSON])->()){
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!){ (data, _, err) in
if err != nil{
print(err!.localizedDescription)
return
}
do{
let serviceLocations = try
JSONDecoder().decode([ServiceLocationJSON].self, from: data!)
completion(serviceLocations)
}
catch{
print(error)
}
}.resume()
}
Build with Xcode 12 and Swift 5
Nevermind, I just solved it. I just need to change coordinateRegion: $region to coordinateRegion: .constant(region)
In my app I have a ViewModel(MapViewModel) class, a UIRepresentable class and ContentView. I am looking for a way to get access to GMSMapView view in the ViewModel that is created as a UIRepresentable class.
ContentView.swift:
import SwiftUI
import Combine
struct ContentView: View {
#State private var selection = 0
#State private var dragOffset = CGSize.zero
#ObservedObject var mapViewModel : MapViewModel = MapViewModel()
var body: some View {
GeometryReader { geo in
TabView(selection: self.$selection) {
MapView()
.edgesIgnoringSafeArea(.all)
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
.tag(0)
Text("Second Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Settings")
}
}
.tag(1)
Text("Third Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Third Page")
}
}
.tag(2)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
MapViewModel.swift:
import Foundation
import Combine
import GoogleMaps
import os
class MapViewModel: NSObject, ObservableObject {
let lm = CLLocationManager()
var myLocations = [CLLocation]()
override init() {
super.init()
lm.delegate = self
lm.desiredAccuracy = kCLLocationAccuracyBest
lm.requestWhenInUseAuthorization()
lm.pausesLocationUpdatesAutomatically = false
lm.allowsBackgroundLocationUpdates = true
lm.startUpdatingLocation()
}
}
extension MapViewModel: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
//self.status = status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
os_log("locationManager:didUpdateLocations: received location",log: Log.general, type: .debug)
}
}
MapView.swift:
import UIKit
import SwiftUI
import GoogleMaps
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: 30.267153, longitude: -97.7430608, zoom: 6.0)
let gmsMapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
gmsMapView.delegate = context.coordinator
return gmsMapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, GMSMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
}
struct GoogleMapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
So any idea on how I can access the gmsMapView object in MapViewModel. I need access to draw lines on the map...Thanks!
I was able to get past this issue by defining the polyline overly value as #Published and accessing it via viewmodel in updateUI. So instead of trying to access the mapview from the viewmodel, I let the view add the polyline overlay. Hope this helps someone else. Thanks
ContentView.swift:
struct ContentView: View {
#State private var selection = 0
#State private var dragOffset = CGSize.zero
#ObservedObject var mapViewModel: MapViewModel
var body: some View {
GeometryReader { geo in
TabView(selection: self.$selection) {
MapView()
.edgesIgnoringSafeArea(.all)
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
.tag(0)
Text("Second Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Settings")
}
}
.tag(1)
Text("Third Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Third Page")
}
}
.tag(2)
}
}
}
}
MapView:
struct MapView: UIViewRepresentable {
#ObservedObject var mapViewModel = MapViewModel()
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: 30.5986015, longitude: -97.8210401, zoom: 20.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.isMyLocationEnabled = true
mapView.animate(toViewingAngle: 45)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
if (mapViewModel.polyline != nil) {
print("updateUIView: Polyline = \(mapViewModel.polyline!.description)")
os_log("updateUIView: Polyline = %{Public}s",log: Log.general, type: .debug, mapViewModel.polyline!.description)
mapViewModel.polyline!.strokeColor = UIColor.red
mapViewModel.polyline!.strokeWidth = 5.0
mapViewModel.polyline!.map = mapView
}
if (mapViewModel.locChanged && mapViewModel.myLocations.count > 0) {
print("updateUIView: Refocus camera on last location")
os_log("updateUIView: Refocus camera on last location",log: Log.general, type: .debug, mapViewModel.polyline!.description)
let camera = GMSCameraPosition.camera(withLatitude: (mapViewModel.myLocations.last?.coordinate.latitude)!, longitude: (mapViewModel.myLocations.last?.coordinate.longitude)!, zoom: 20.0)
let _ = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, GMSMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
}
MapViewModel:
class MapViewModel: NSObject, ObservableObject {
.
.
.
#Published var polyline: GMSPolyline?
#Published var locChanged: Bool = false
override init() {
super.init()
.
.
.
polyline = nil
}
.
.
.
func draw(myLocations: [CLLocation], color: UIColor) {
os_log("MapViewController: Drawing Track for last two Locations",log: Log.general, type: .info)
print("MapViewController: Drawing Track for last two Locations")
let path = GMSMutablePath()
let c1 = myLocations[myLocations.count - 1].coordinate
let c2 = myLocations[myLocations.count - 2].coordinate
path.add(c1)
path.add(c2)
polyline = GMSPolyline(path: path)
print("draw: Polyline = \(polyline!.description)")
polyline!.strokeColor = color
polyline!.strokeWidth = 5.0
}
.
.
.
extension MapViewModel: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
os_log("locationManager:didUpdateLocations: received location",log: Log.general, type: .debug)
guard let lastLocation: CLLocation = locations.last else {
os_log("locationManager:didUpdateLocations: received null location",log: Log.general, type: .debug)
return
}
if (myLocations.count >= 2) {
os_log("MapViewController: updateLocation: Calling draw method with count = %{Public}s",log: Log.general, type: .debug, myLocations.count.description)
self.draw(myLocations: self.myLocations, color:.red)
}
.
.
.
} else {
os_log("LocationManager: Bad Location...",log: Log.general, type: .error)
badLocationCount += 1
locChanged = false
}
}
}
I tap on picker it will open screen with list of element on that screen, can we add Search Bar?
I implemented Country Picker, in the country list I am showing country name and country code so on that list screen add Search bar find out country easily.
struct ContentView: View {
//All Country get from the plist with country Code and Coutnry Name.
let countyList = Country().getAllCountyInfo()
// Selected Country
#State private var selectedCountry = 0
var body: some View {
NavigationView {
Form {
Picker(selection: $selectedCountry, label: Text("Country")) {
ForEach(countyList) { country in
HStack{
Text(country.countryName ?? "")
Spacer()
Text(country.countryCode ?? "")
}
}
}
}
.navigationBarTitle("Select country picker")
}
}
}
by run above code it will open a country list like above screen.
on the above screen (country list).
How can I add search bar to filter country data?
yes you can!
SearchBar code:
struct SearchBar: View {
#Binding var text: String
#State var isEditing: Bool = false
#ObservedObject var vc = VC()
var body: some View {
HStack {
TextField("Search Countries", text: $text)
.padding(7)
.padding(.horizontal, 25)
.background(Vc.color2)
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
UIApplication.shared.endEditing(true)
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
Picker with Search Bar:
struct locc: View {
#State var locationsSelection: [String] = //Your Array
#State var searchText = ""
#State var locationSelection: Int = 0
var body: some View {
Picker(selection: $locationSelection, label: Text("Country")) {
SearchBar(text: $searchText)
if self.searchText != "" {
ForEach(0..<locationsSelection.filter{$0.hasPrefix(searchText)}.count) {
Text(locationsSelection.filter{$0.hasPrefix(searchText)}[$0]).tag($0)
}.resignKeyboardOnDragGesture()
}else if self.searchText == "" {
ForEach(0..<locationsSelection.count) {
Text(locationsSelection[$0]).tag($0)
}.resignKeyboardOnDragGesture()
}
}
}
}
Note: I am using the locc struct in another view containing the Form.
I solve this in my blog post here: https://roddy.io/2020/09/07/add-search-bar-to-swiftui-picker/
Create the UIViewRepresentable:
struct SearchBar: UIViewRepresentable {
#Binding var text: String
var placeholder: String
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.placeholder = placeholder
searchBar.autocapitalizationType = .none
searchBar.searchBarStyle = .minimal
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UISearchBarDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
}
}
And then it's easy to embed it in your Picker:
struct FormView: View {
let countries = ["Brazil", "Canada", "Egypt", "France", "Germany", "United Kingdom"]
#State private var pickerSelection: String = ""
#State private var searchTerm: String = ""
var filteredCountries: [String] {
countries.filter {
searchTerm.isEmpty ? true : $0.lowercased().contains(searchTerm.lowercased())
}
}
var body: some View {
NavigationView {
Form {
Picker(selection: $pickerSelection, label: Text("")) {
SearchBar(text: $searchTerm, placeholder: "Search Countries")
ForEach(filteredCountries, id: \.self) { country in
Text(country).tag(country)
}
}
}
}
}
}