i am trying to use MKLocalSearch Api in Swift. But I can't get it to work. The error is coming from
var search:MKLocalSearch = MKLocalSearch.init(request)
I read the documentation, and it state the method name is init(request:) I am not sure what i did wrong. please advice. :)
var request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchTextFiled.text
println(searchTextFiled.text)
request.region = self.mapView.region //need to define region later
var search:MKLocalSearch = MKLocalSearch.init(request)
search.startWithCompletionHandler {
(response:MKLocalSearchResponse!, error:NSError!) in
if !error {
var placemarks:NSMutableArray = NSMutableArray()
for item in response.mapItems {
placemarks.addObject(placemarks)
}
self.mapView.removeAnnotations(self.mapView.annotations)
self.mapView.showAnnotations(placemarks, animated: true)
} else {
}
}
Change this line:
var search:MKLocalSearch = MKLocalSearch.init(request)
To:
var search:MKLocalSearch = MKLocalSearch.init(request: request)
It's made a little confusing by the naming involved in this case - the first part, request: is a label that tells Swift which init function to call - in this case, it's mapping back to the Objective-C initWithRequest: method.
The second part, request is your MKLocalSearchRequest variable that you're passing to the init function. So the Objective-C equivalent is this:
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
There's a whole chapter titled "Initialization" in Apple's The Swift Programming Language book, available on the iBooks store and as a PDF here, albeit with worse (bordering on terrible) formatting in the PDF.
That's incorrect syntax for init. They're implied by Swift, so you'd use
var x = MKLocalSearch(request: request)
not
var x = MKLocalSearch.init(request)
Related
I have the following class with a static method that uses MKDirections to calculate custom routes between two coordinates. Once it finishes calculating, the method uses a delegate to pass the route (an MKPolyline object) to the View Controller which adds it to a MapView as an overlay. Every route is assigned a title that determines in which color the route is rendered on the map.
class NavigationInterface {
weak static var routeDelegate: RouteDelegate!
static func addRouteFromTo(sourceCoor: CLLocationCoordinate2D, destinationCoor: CLLocationCoordinate2D, transportTypeString: String)
{
let sourcePlacemark = MKPlacemark(coordinate: sourceCoor)
let destinationPlacemark = MKPlacemark(coordinate: destinationCoor)
//var route = MKRoute()
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: sourcePlacemark)
request.destination = MKMapItem(placemark: destinationPlacemark)
request.requestsAlternateRoutes = false
//get MKDirectionsTransportType based on String identifier
request.transportType = getTransportType(transportTypeString: transportTypeString)
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
if let directionResponse = response?.routes.first {
let route = directionResponse.polyline
route.title = transportTypeString
print("Got Here")
self.routeDelegate!.didAddRoute(route: route)
}
}
}
The delegate is defined through the following protocol:
protocol RouteDelegate: class {
func didAddRoute(route: MKPolyline)
func didAddBoundary(boundary: MKPolygon)
}
The View Controller implements the delegate as follows:
class MapViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
...
override func viewDidLoad() {
super.viewDidLoad()
NavigationInterface.routeDelegate = self
}
extension MapViewController: RouteDelegate {
// delegate Method
// called in Navigation Interface
func didAddRoute(route: MKPolyline) {
mapView.add(route)
}
func didAddBoundary(boundary: MKPolygon) {
mapView.add(boundary)
}
}
Now I have attempted to write a UnitTest which checks whether the delegate method "didAddRoute" returns the correct route
For this purpose I've created a test class "NavigationTests" which implements the RouteDelegate protocol and an test method that calculates a route and then evaluates the route returned from the "NavigationTests" protocol implementation of "didAddRoute":
class NavigationTests: XCTestCase, RouteDelegate {
var routes = [MKPolyline]()
var asyncExpectation: XCTestExpectation?
func didAddRoute(route: MKPolyline) {
routes.append(route)
asyncExpectation?.fulfill()
}
...
func testaddRouteFromTo(){
NavigationInterface.routeDelegate = self
asyncExpectation = expectation(description: "routes returned from delegate method")
NavigationInterface.addRouteFromTo(sourceCoor: CoordinateA, destinationCoor: CoordinateB, transportTypeString: "roadTravel")
let result = XCTWaiter.wait(for: [self.asyncExpectation!], timeout: 2.0)
if result == XCTWaiter.Result.completed {
let route = self.routes.first
XCTAssert(route!.title == "roadTravel", "failed to retrieve correct route")
print(route!.title)
} else {
XCTFail()
}
}
}
Now this test method randomly returns routes from the MapViewController implementation of RouteDelegate instead of the NavigationTests implementation. W
How can I avoid these unwanted references to the MapViewController and why is it created at all since I do not instantiate it in the test?
Ideally I would like to prevent the MapViewController from being instantiated when running this test class since it is not required for the Unit Test.
How can I make sure that only the NavigationTests implementation of the RouteDelegate is used?
Statics vs. tests
Because addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:) is a static method, you have made NavigationInterface.routeDelegate static as well. When your tests run, they are setting a global variable. This means the tests have side effects which last beyond the scope of the tests.
Here are a couple of approaches to prevent this from happening:
a) Create a setUp() and tearDown(). In setUp(), save the old value of NavigationInterface.routeDelegate before overwriting it to self. In tearDown(), restore the old value.
b) Change from statics to an object. As a general rule, statics make things harder to test.
Prefer b). It is safer, and lets the pressure of testability improve your design.
…I don't see any references to MapViewController in your test. Was it created by your application delegate?
How to test an asynchronous call?
Now to your larger question. A test that does actual networking is slow and fragile. It depends on your network conditions. It depends on the back end. It introduces a time lag.
You would be better served by restructuring your code so that you can test the following:
Are you creating the correct MKDirectionsRequest?
Are you handling the response correctly?
This will be expressed in at least 2 tests, but probably more. Once you can independently test response handling, then you can test errors as well as successful responses.
So how do you test "create the response" independently from "handle the response"? By doing this work in separate methods. The tests can then just call these methods.
There is no need to test that Apple makes a network call, or does something on the back end, or sends a response. If you follow this approach, the need for asynchronous tests drops dramatically.
I hope this helps. If you need clarification, please ask. For more thoughts on how "the way Apple shows us to write code isn't good testable design," see https://qualitycoding.org/design-sense/
I just converted my project to swift 4, but I having error when I emulate my app with SWXMLHash:
var code: Int! = nil
var message: String! = nil
var paramsContent: String! = nil
let xmlStrData = SWXMLHash.lazy(strData)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let appDel: AppDelegate = UIApplication.shared.delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let simulationParamsDescription = NSEntityDescription.entity(forEntityName: "SimulationParams", in: context)
let simulationParams = NSManagedObject(entity: simulationParamsDescription!, insertInto: context)
code = Int((xmlStrData["SOAP-ENV:Envelope"]["SOAP-ENV:Body"]["ns1:GetSimulationParamsResponse"]["GetSimulationParamsReturn"]["code"].element?.text)!)!
message = xmlStrData["SOAP-ENV:Envelope"]["SOAP-ENV:Body"]["ns1:GetSimulationParamsResponse"]["GetSimulationParamsReturn"]["message"].element?.text
When i'm trying to get the code node value, I get this error:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
It seems likely something is going wrong with the parsing here - most probably the structure of the XML response is not exactly what you think it is.
In general in Swift it's a good idea to steer away from force unwrapping optionals with ! because if there isn't anything there you are guaranteeing that the app will crash. It's better to use if lets and guard statements, even if it makes the code uglier, just because it's safer.
To debug this, I'd rewrite your the last two lines as a series of guards (not sure what the types involved are, so you'll have to fill in $DICT_TYPE_HERE appropriately). You may not need to do it for every level, but I'd definitely avoid accessing 4-5 levels on one line - it's just asking for trouble.
guard let envelope = xmlStrData["SOAP-ENV:Envelope"] else as? $DICT_TYPE_HERE {
//handle failure
return
}
guard let body = envelope["SOAP-ENV:Body"] else as? $DICT_TYPE_HERE {
//handle failure
return
}
.
.
until you find the problem
Ideally you should be using an XML object mapper, which allows you to declare Swift objects that can be translated back and forth to XML, but unfortunately I can't find an obvious one for Swift (there are loads for JSON, including the shiny new Codable protocol made by Apple itself).
I am trying to edit google sheets from within swift. I used the original Quickstart tutorial by google, but since that is for reading a spreadsheet, I had to slightly edit it for writing to a spreadsheet.
//Signing in as normal from the Google Quickstart
//This function is the one I edited, to write to the spreadsheet
func listMajors() {
output.text = "Getting sheet data..."
let spreadsheetId = "**********************"
let range = "Sheet 1!A2"
let valueRange = GTLRSheets_ValueRange.init()
valueRange.values = [["Hello" as Any]]
let query = GTLRSheetsQuery_SpreadsheetsValuesUpdate.query(withObject: valueRange, spreadsheetId: spreadsheetId, range: range)
query.valueInputOption = "USER_ENTERED"
service.executeQuery(query, delegate: self, didFinish: #selector(displayResultWithTicket(ticket:finishedWithObject:error:)))
}
func displayResultWithTicket(ticket: GTLRServiceTicket, finishedWithObject result : GTLRSheets_ValueRange, error : NSError?) {
if let error = error {
showAlert(title: "Error", message: error.localizedDescription)
return
}
print(result)
}
It seemed to work fine for the reading spreadsheet, but it is not working for writing to spreadsheet. The app launches, then i get an error alert with this:
Sorry to add solution in objective-C, but i think you will be able to manage conversion
I just have the same problem today.
The solution is simple, when initializing the GIDSignIn
// Configure Google Sign-in.
GIDSignIn* signIn = [GIDSignIn sharedInstance];
signIn.delegate = self;
signIn.uiDelegate = self;
signIn.scopes = [NSArray arrayWithObjects:kGTLRAuthScopeSheetsSpreadsheets, nil];
[signIn signInSilently];
Note the scope value. In the quickStart tutorial, kGTLRAuthScopeSheetsSpreadsheetsReadonly is used.
Romain
Getting extra argument error call in relation to the 'items' constant below. Has this changed following Xcode 7.0?
override func viewDidLoad() {
super.viewDidLoad()
let fm = NSFileManager.defaultManager()
let path = NSBundle.mainBundle().resourcePath!
let items = fm.contentsOfDirectoryAtPath(path, error: nil)
for item in items as! [String] {
if item.hasPrefix("nssl") {
objects.append(item)
}
}
According to Apple for contentsOfDirectoryAtPath:
In Swift, this method returns a nonoptional result and is marked with
the throws keyword to indicate that it throws an error in cases of
failure.
You call this method in a try expression and handle any errors in the
catch clauses of a do statement, as described in Error Handling
in The Swift Programming Language (Swift 2).
The new/correct way to do this in Swift 2.0 is this:
let fm = NSFileManager.defaultManager()
let path = NSBundle.mainBundle().resourcePath!
do {
let items = try fm.contentsOfDirectoryAtPath(path)
for item in items as! [String] {
if item.hasPrefix("nssl") {
objects.append(item)
}
}
}
catch let error as NSError {
error.description
}
You need to adopt to new error handling techniques added in Swift 2.0. You need to use do..try..catch for fixing this issue (error handling)
Change your code like:
override func viewDidLoad()
{
super.viewDidLoad()
let fm = NSFileManager.defaultManager()
let path = NSBundle.mainBundle().resourcePath!
do
{
let items = try fm.contentsOfDirectoryAtPath(path)
for item in items
{
if item.hasPrefix("nssl")
{
objects.append(item)
}
}
}
catch
{
// Handle error here
}
}
Also you don't need to use items as! [String], because fm.contentsOfDirectoryAtPath returns an array of strings [String]
Refer Swift Error Handling for more details. Also check contentsOfDirectoryAtPath:error: for checking that new method syntax.
My bet is, it's because in Swift 2 error handling changed. The method you are calling no longer takes the second "error" parameter, and instead "throws".
Read the Swift 2 docs.
You need to convert to the new do-try-catch construct. Xcode should have suggested it for you...?
I am trying to reference parse.com variables throughout my code. I'm not 100% sure how to. Where am I going wrong?
When I do this:
message["title"] = messageTitle.text
I get the error PFObject? does not have a member named subscript
Or
messageTitle.text = self.message!.title
I get the error PFObject does not have a member named 'title'
Or
messageTitle.text = message["title"]
I get the error AnyOject is not convertible to String
To give you some background:
I have a parse backend with a Message table. I refer to the table like so:
PFObject(className: "Message")
I have a messages view controller which includes a collection view MessagesViewController.swift
I have a collection view cell class which is used to display the messages on the collection view MessageCell.swift
I have a message view controller for editing a message MessageViewController.swift
I want to be able to select a cell on the MessagesViewController which will then be used by the MessageViewController to display the selected message
I declare the local message variable:
var messages = [PFObject]()
On long press:
let storyboard = self.storyboard
let vc = storyboard!.instantiateViewControllerWithIdentifier("MessageViewController") as MessageViewController
vc.message = self.messages[self.visibleCellIndex]
navigationController?.pushViewController(vc, animated: true)
Message viewController I declare its local variable and set the textfield to be equal to the message's title
var message = PFObject?()
override func viewDidLoad() {
super.viewDidLoad()
messageTitle.text = self.message.title
}
I also want to save any changes to the message or create a new message if there isn't one already
func saveButtonPressed() {
if message == nil {
message = PFObject(className: "Message")
}
message["title"] = messageTitle.text
message.saveInBackground()
}
Then I want to be able to create a message cell by binding it to a message in cellForRowAtIndexPath
Messages viewController
cell.bindWithMessage(messages[indexPath.row])
MessageCell
var message = PFObject()
func bindWithMessage(aMessage: PFObject) {
message = aMessage
messageTitle.text = message["title"]
}
According to the error message, the problem is that message is an optional. So
message?["title"] = messageTitle.text
should help.
UPDATED
Take a look at the declaration of message:
var message = PFObject?()
Is it an optoinal? It is. Can you use the syntax message["title"] = messageTitle.text on it? No, you cannot. You need to unwrap it. The question states nowhere, that he wanted to you the syntax message.title, and I cannot read minds.
when declaring the var use:
var myItem : PFObject!
And then when you go to use it, you use optional binding:
If let mySting = myItem["key"] as? String {
label.text = myString
}