if (stringToURL?.isValidURL)! <-- Not sure why the complier requires optional chaining on stringToURL when it is safely declared in Guard statement. Also, string extension for isValidURL: Bool always returns Bool but compiler still wants unwrapping.
In this example, annotation.subtitle should already be string in URL format but I wanted to confirm.
Trying to use variables defined in guard becomes more convoluted than expected because further unwrapping is needed. Now I feel that I'm making a few lines of code overly complicated to follow/read with my implementations.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let backupURL = URL(string: "https://www.google.com")!
guard let currentAnnotation = view.annotation, var stringToURL = currentAnnotation.subtitle else {
// currentAnnotation has blank subtitle. Handle by opening up any website.
UIApplication.shared.open(backupURL, options: [:])
return
}
if (stringToURL?.isValidURL)!{
stringToURL = stringToURL?.prependHTTPifNeeded()
if let url = URL(string: stringToURL!){
UIApplication.shared.open(url, options: [:])
} else {
UIApplication.shared.open(backupURL, options: [:])
}
}
}
extension String {
var isValidURL: Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.endIndex.encodedOffset)) {
// it is a link, if the match covers the whole string
return match.range.length == self.endIndex.encodedOffset
} else {
return false
}
}
func prependHTTPifNeeded()-> String{
let first4 = self.prefix(4)
if first4 != "http" {
return "http://" + self
} else {
return self
}
}
}
The code block executes properly.
annotation.subtitle = "https://www.yahoo.com" <--- yahoo opens
annotation.subtitle = "www.yahoo.com" <--- yahoo opens
annotation.subtitle = "yahoo" <--- google.com opens because we did not have a valid URL string
The problem is that currentAnnotation.subtitle is a String??, because subtitle is not only a String?, itself, but it is also an optional property of MKAnnotation protocol. So a simple unwrap only verifies that the optional protocol subtitle was implemented, but not that the resulting String? was not nil. You have to unwrap that, too.
But you can do guard var stringToURL = view.annotation?.subtitle as? String else { ... }, and it will be properly unwrapped to a String:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let backupURL = URL(string: "https://www.google.com”)!
guard var stringToURL = view.annotation?.subtitle as? String else {
UIApplication.shared.open(backupURL)
return
}
if stringToURL.isValidURL {
stringToURL = stringToURL.prependHTTPifNeeded()
let url = URL(string: stringToURL) ?? backupURL
UIApplication.shared.open(url)
}
}
Note, that will open the backupURL if no string is provided, but if a string was provided and wasn’t a valid URL, it won’t do anything. So perhaps you meant the following, which will open backupURL if it can’t open stringToURL:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let backupURL = URL(string: "https://www.google.com")!
guard var stringToURL = view.annotation?.subtitle as? String,
stringToURL.isValidURL else {
UIApplication.shared.open(backupURL)
return
}
stringToURL = stringToURL.prependHTTPifNeeded()
let url = URL(string: stringToURL) ?? backupURL
UIApplication.shared.open(url)
}
Where:
extension String {
var isValidURL: Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let range = NSRange(startIndex..., in: self)
return detector.firstMatch(in: self, range: range)?.range == range
}
func prependHTTPifNeeded() -> String{
if prefix(4) != "http" {
return "http://" + self
} else {
return self
}
}
}
Related
I have been trying desperately for the past few hours to make a phone call with swift when I call this function.
func callPhone(phoneNumber: String){
guard let url = URL(string:"telprompt:\(phoneNumber)") else {
print("failed to load url, phone number: \(phoneNumber)")
return
}
UIApplication.shared.open(url)
}
For some reason it doesn't work, I have researched online everywhere and still couldn't find a problem to why this wouldn't work. Some numbers would occasionally work, like if I tried "123456789" it would work. but if i try a different number it wouldn't work; The guard let statement doesn't work and the url would be empty.
Please help me. Any feedback would be very much appreciated.
Edit: So I found out when it is not working but I am not sure how to solve it. It doesn't work when I get the phone number from apple maps, but when I hard code the phone number it works.
Here is my full code:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var map: MKMapView!
let locationManager = CLLocationManager()
let regionMeters : Double = 10000
override func viewDidLoad() {
super.viewDidLoad()
map.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
map.showsUserLocation = true
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionMeters, longitudinalMeters: regionMeters)
map.setRegion(region, animated: true)
}
searchLocations(search: "Restaurant")
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "Place"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
let btn = UIButton(type: .contactAdd)
annotationView!.rightCalloutAccessoryView = btn
} else {
annotationView!.annotation = annotation
}
return annotationView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let subtitle = view.annotation?.subtitle as! String
let splitted = subtitle.components(separatedBy: "+")
let number = splitted.last as! String
let trimmedNumber : String = number.components(separatedBy: [" ", "-", "(", ")"]).joined()
CallOnPhone(phoneNumber: trimmedNumber)
}
func searchLocations(search : String){
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = search
searchRequest.region = map.region
let activeSearch = MKLocalSearch(request: searchRequest)
activeSearch.start { (response, err) in
if response == nil {
print("no request")
} else {
for item in (response?.mapItems)!{
let annotation = MKPointAnnotation()
annotation.title = item.name
if let phone = item.phoneNumber {
annotation.subtitle = "Phone Number: \(phone as String)"
}
annotation.coordinate = item.placemark.coordinate
self.map.addAnnotation(annotation)
}
}
}
}
#objc func CallOnPhone(phoneNumber: String){
let newStringPhone = phoneNumber.replacingOccurrences(of: " ", with: "", options: .literal, range: nil)
print(newStringPhone)
if newStringPhone != ""{
if let url = URL(string: "tel://\(newStringPhone)"), UIApplication.shared.canOpenURL(url) {
if #available(iOS 10, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
}
}
}
}
Thanks,
Try trimming the phone number.
let myphone:String = phoneNumber.trimmingCharacters(in:.whitespacesAndNewlines)
Full code:
if let url = URL(string: "tel://\(myphone)"),UIApplication.shared.canOpenURL(url) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:], completionHandler:nil)
} else {
UIApplication.shared.openURL(url)
}
} else {
Util.shared.showToast(message: "Can't make call from this device", view: self.view)
}
Below code show calling a number on button click. Also removing space if phonenum has spaces.
var phoneNumber = "Phone Number: 1234567"
var finalNumber = ""
let number = phoneNumber.split(separator: ":")
let tempNum = "\(number.last ?? "")"
print(tempNum)
let trimmedNumber : String = tempNum.components(separatedBy: [" ", "-", "(", ")"]).joined()
print(trimmedNumber)
finalNumber = trimmedNumber
print(finalNumber)
btn_Call.addTarget(self, action: #selector(CallOnPhone), for: .touchUpInside)
#objc func CallOnPhone(sender:UIButton){
let newStringPhone = finalNumber.replacingOccurrences(of: " ", with: "", options: .literal, range: nil)
print(newStringPhone)
if newStringPhone != ""{
if let url = URL(string: "tel://\(newStringPhone)"), UIApplication.shared.canOpenURL(url) {
if #available(iOS 10, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
}
}
}
I am trying to create a seat map layout functionality that takes the coordinates and create a map i want to provide the value of the sections that the map contains. I am using custom collectionViewLayout to create the cells but i am getting that error in the title .
Here is my protocol-
protocol SeatMapDelegate: class {
func getSectionCoordinates() -> [Int]
}
Definition -
func getSectionCoordinates() -> [Int] {
return sectionHeader
}
and then i am assigning the values to the array
var sectionHeader = [Int]()
sectionHeader=(delegate?.getSectionCoordinates())!
below code is my project for search and find coordinate on the map:
Maybe help you
// ViewController.swift
// MapKit Starter
//
// Created by Ehsan Amiri on 10/25/16.
// Copyright © 2016 Ehsan Amiri. All rights reserved.
//
import UIKit
import MapKit
import Foundation
class ViewController: UIViewController {
#IBOutlet var mapView: MKMapView?
#IBOutlet weak var text: UITextField!
var index = 0
var indexx = 0
let locationManager = CLLocationManager()
var picName:String?
var place :MKAnnotation?
var places = [Place]()
var place1 :MKAnnotation?
var places1 = [Place]()
override func viewDidLoad() {
super.viewDidLoad()
//downpic()
self.requestLocationAccess()
}
override func viewWillAppear(_ animated: Bool) {
let defaults = UserDefaults.standard
let age = defaults.integer(forKey: "maptype")
switch (age) {
case 0:
mapView?.mapType = .standard
case 1:
mapView?.mapType = .satellite
case 2:
mapView?.mapType = .hybrid
default:
mapView?.mapType = .standard
}
}
#IBAction func info(_ sender: Any) {
}
override var prefersStatusBarHidden: Bool {
return true
}
func requestLocationAccess() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways, .authorizedWhenInUse:
return
case .denied, .restricted:
print("location access denied")
default:
locationManager.requestWhenInUseAuthorization()
}
}
#IBAction func textField(_ sender: Any) {
mapView?.removeOverlays((mapView?.overlays)!)
mapView?.removeAnnotations((mapView?.annotations)!)
self.server()
_ = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(self.server), userInfo: nil, repeats: true)
let when = DispatchTime.now() + 1.5
DispatchQueue.main.asyncAfter(deadline: when) {
if self.indexx != 0 {
self.addAnnotations()
self.addPolyline()
}
}
}
#objc func server() {
place = nil
places = [Place]()
place1 = nil
places1 = [Place]()
indexx = 0
let id = text.text
print("id=\(id!)")
let url = URL(string: "my server")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "id=\(id!)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { // check for fundamental networking error
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(String(describing: responseString))")
let stringgg = "notFound\n\n\n\n"
if responseString == stringgg {
print(stringgg)
}else{
let json = try! JSONSerialization.jsonObject(with: data, options: [])
let betterJSON = json as! NSArray
let jsonCount = betterJSON.count
print(betterJSON)
for item in betterJSON {
self.indexx += 1
let dictionary = item as? [String : Any]
let title = dictionary?["title"] as? String
let subtitle = dictionary?["description"] as? String
let latitude = dictionary?["latitude"] as? Double ?? 0, longitude = dictionary?["longitude"] as? Double ?? 0
self.place = Place(title: title, subtitle: subtitle, coordinate: CLLocationCoordinate2DMake(latitude , longitude ))
self.places.append(self.place as! Place)
print("latttt",longitude)
if self.indexx == 1{
let shipid = UserDefaults.standard
shipid.set(title, forKey: "origin")
shipid.set(subtitle, forKey: "date")
}
if jsonCount == self.indexx{
let shipid = UserDefaults.standard
shipid.set(title, forKey: "location")
self.place1 = Place(title: title, subtitle: subtitle, coordinate: CLLocationCoordinate2DMake(latitude , longitude ))
self.places1.append(self.place1 as! Place)
}
}
}
}
task.resume()
let when = DispatchTime.now() + 1.5
DispatchQueue.main.asyncAfter(deadline: when) {
if self.indexx != 0 {
self.addAnnotations()
self.addPolyline()
}
}
}
func addAnnotations() {
print("hhhh",places)
mapView?.delegate = self
mapView?.removeAnnotations((mapView?.annotations)!)
mapView?.addAnnotations(places1)
let overlays = places1.map { MKCircle(center: $0.coordinate, radius: 100) }
mapView?.addOverlays(overlays)
}
func addPolyline() {
var locations = places.map { $0.coordinate }
let polyline = MKPolyline(coordinates: &locations, count: locations.count)
// print("Number of locations: \(locations.count)")
index = locations.capacity
mapView?.add(polyline)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
else {
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "annotationView") ?? MKAnnotationView()
annotationView.image = UIImage(named:"place icon")
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.canShowCallout = true
return annotationView
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKCircle {
let renderer = MKCircleRenderer(overlay: overlay)
renderer.fillColor = UIColor.black.withAlphaComponent(0.5)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 2
return renderer
} else if overlay is MKPolyline {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.orange
renderer.lineWidth = 2
return renderer
}
return MKOverlayRenderer()
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
//guard let annotation = view.annotation as? Place, let title = annotation.title else { return }
let shipidname = text.text
let shipid = UserDefaults.standard
shipid.set(shipidname, forKey: "shipid")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewController(withIdentifier: "shipinfo")
self.present(secondViewController, animated: true, completion: nil)
}
}
I have a URL String "http:///blaBla?id=Testid851211" and I just want to get "851211".
Below is my code :-
let url: NSURL = NSURL(string: urlString)!
Helper.sharedInstance.Print(url.query as AnyObject)
if (url.query?.localizedStandardContains("testKey"))! {
//TestKey
Helper.sharedInstance.Print(url.query as AnyObject)
let testValue = getQueryStringParameter(url: urlString, param: "testKey")
Helper.sharedInstance.Print(testValue as AnyObject)
}
else if (url.query?.localizedStandardContains("testID"))! {
//TestID
Helper.sharedInstance.Print(url.query as AnyObject)
}
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}
I am getting id = Testid851211 but I want only "851211".
Use a regular expression filtering out numbers only:
let urlString = "http:///blaBla?id=Testid851211"
let pattern = "[0-9]+"
if let matchRange = urlString.range(of: pattern, options: .regularExpression) {
print(urlString[matchRange])
}
Works as long as your URLs don’t have numbers anywhere else.
Try this extension:
extension String {
func getNeededText(for url: String) -> String {
guard range(of: url) != nil else { return "" }
return replacingOccurrences(of: url, with: "")
}
}
Usage:
let predefinedHost = "http:///blaBla?id=Testid"
let url = "http:///blaBla?id=Testid851211"
url.getNeededText(for: predefinedHost) // prints "851211"
I do have the problem that I do not know how to specify which UIButton .DetailDisclosure type is pressed. I do have multiple Annotations and I want to show the details of this specific Annotation. I hope that question is clear and someone can help me.
Thank you in advance.
This is my code right now
import UIKit
import MapKit
import CoreLocation
class FirstViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var myURL = NSURL(string: "http://www...");
var Daten = NSMutableArray()
var Test = ""
#IBOutlet weak var Mapkit: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
let request = NSMutableURLRequest(URL: myURL!)
request.addValue("application/json", forHTTPHeaderField: "Accept")
_ = NSURLSession.sharedSession().downloadTaskWithRequest(request)
let Session = NSURLSession.sharedSession()
Session.dataTaskWithRequest(request)
do {
let data = NSData(contentsOfURL: myURL!)
_ = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
self.Daten = try (NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary)["Ort"] as! NSMutableArray
for parseJSON in self.Daten{
let resultName:String = parseJSON["Name"] as! String!;
let länge:String = parseJSON["breite"] as! String!;
let breite:String = parseJSON["laenge"] as! String!;
self.Test = resultName
}
for Pin in self.Daten{
let Name = Pin["Name"] as! String!
let longitude = Pin["laenge"] as! String!
let breite = Pin["breite"] as! String!
let longitudeDouble = Double(longitude!)
let breitedouble = Double(breite!)
let Location = MKPointAnnotation()
let Coordinat = CLLocationCoordinate2DMake(longitudeDouble!, breitedouble!)
Location.coordinate = Coordinat
Location.title = Name
self.Mapkit.addAnnotation(Location)
}
}
catch {
print(error)
}
// more here
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var anView:MKAnnotationView = MKAnnotationView()
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation is MKUserLocation) {
//if annotation is not an MKPointAnnotation (eg. MKUserLocation),
//return nil so map draws default view for it (eg. blue dot)...
return nil
}
let reuseId = "test"
if let annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKPinAnnotationView(annotation:annotation, reuseIdentifier:reuseId)
annotationView.enabled = true
annotationView.canShowCallout = true
let btn = UIButton(type: .DetailDisclosure)
annotationView.rightCalloutAccessoryView = btn
return annotationView
}
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
//I don't know how to convert this if condition to swift 1.2 but you can remove it since you don't have any other button in the annotation view
if (control as? UIButton)?.buttonType == UIButtonType.DetailDisclosure {
mapView.deselectAnnotation(view.annotation, animated: false)
performSegueWithIdentifier("Infos", sender: view)
}
}
}
The MKAnnotation is passed to you in func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl). You can use it to search for the right action in a hash map.
Or even simpler, test for equality if only one annotation is different.
I try to get URLs in text. So, before, I used such an expression:
let re = NSRegularExpression(pattern: "https?:\\/.*", options: nil, error: nil)!
But I had a problem when a user input URLs with Capitalized symbols (like Http://Google.com, it doesn't match it).
I tried:
let re = NSRegularExpression(pattern: "(h|H)(t|T)(t|T)(p|P)s?:\\/.*", options: nil, error: nil)!
But nothing happened.
You turn off case sensitivity using an i inline flag in regex, see Foundation Framework Reference for more information on available regex features.
(?ismwx-ismwx)
Flag settings. Change the flag settings. Changes apply to the portion of the pattern following the setting. For example, (?i) changes to a case insensitive match.The flags are defined in Flag Options.
For readers:
Matching an URL inside larger texts is already a solved problem, but for this case, a simple regex like
(?i)https?://(?:www\\.)?\\S+(?:/|\\b)
will do as OP requires to match only the URLs that start with http or https or HTTPs, etc.
Swift 4
1. Create String extension
import Foundation
extension String {
var isValidURL: Bool {
guard !contains("..") else { return false }
let head = "((http|https)://)?([(w|W)]{3}+\\.)?"
let tail = "\\.+[A-Za-z]{2,3}+(\\.)?+(/(.)*)?"
let urlRegEx = head+"+(.)+"+tail
let urlTest = NSPredicate(format:"SELF MATCHES %#", urlRegEx)
return urlTest.evaluate(with: trimmingCharacters(in: .whitespaces))
}
}
2. Usage
"www.google.com".isValidURL
Try this - http?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?
let pattern = "http?://([-\\w\\.]+)+(:\\d+)?(/([\\w/_\\.]*(\\?\\S+)?)?)?"
var matches = [String]()
do {
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
let nsstr = text as NSString
let all = NSRange(location: 0, length: nsstr.length)
regex.enumerateMatchesInString(text, options: NSMatchingOptions.init(rawValue: 0), range: all, usingBlock: { (result, flags, _) in
matches.append(nsstr.substringWithRange(result!.range))
})
} catch {
return [String]()
}
return matches
Make an exension of string
extension String {
var isAlphanumeric: Bool {
return rangeOfString( "^[wW]{3}+.[a-zA-Z]{3,}+.[a-z]{2,}", options: .RegularExpressionSearch) != nil
}
}
call using like this
"www.testsite.edu".isAlphanumeric // true
"flsd.testsite.com".isAlphanumeric //false
My complex solution for Swift 5.x
ViewController:
private func loadUrl(_ urlString: String) {
guard let url = URL(string: urlString) else { return }
let request = URLRequest(url: url)
webView.load(request)
}
UISearchBarDelegate:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else { return }
if !text.isUrl() {
let finalUrl = String(format: "%#%#", "https://www.google.com/search?q=", text)
loadUrl(finalUrl)
return
}
if text.starts(with: "https://") || text.starts(with: "http://") {
loadUrl(text)
return
}
let finalUrl = String(format: "%#%#", "https://", text)
loadUrl(finalUrl)
}
String extension:
extension String {
func isUrl() -> Bool {
guard !contains("..") else { return false }
let regex = "((http|https)://)?([(w|W)]{3}+\\.)?+(.)+\\.+[A-Za-z]{2,3}+(\\.)?+(/(.)*)?"
let urlTest = NSPredicate(format:"SELF MATCHES %#", regex)
return urlTest.evaluate(with: trimmingCharacters(in: .whitespaces))
}
}