A way to search location without Map being visible? - swift

I'm new to iOS developing, any help or resources are appreciated!
Is there a way to search for a city, town, state or country using a search bar without having the map visible? I basically want to have the customer type in any location, select it and then view search results in that location that the client added on their controller/log in. I've already created the log in/sign up, eventually I want to integrate Stripe for each search result.
I've tried looking for tutorials online, but only seem to find tutorials based off of having the actual Map visible and inputting regular text.

Yes, you can initialize a uisearchbar without tying it to a map. You then make your class a delegate and tell it how to handle searches. The following is in Swift 2
First, make your class a delegate:
class yourClass: UISearchBarDelegate
Initialize the controller
var searchController: UISearchController!
searchController = UISearchController(nil) // nil means you want to show the results on the same page as the search
Then, in whatever action will present the search:
self.presentViewController(searchController, animated: true, completion: nil)
Now you tell it how to handle a search using one or more of these methods:
func searchBar(UISearchBar, textDidChange: String) //the user changed the search text
func searchBarTextDidEndEditing(UISearchBar) // user stopped changing search text
func searchBarSearchButtonClicked(searchBar: UISearchBar) // user clicked "Search"
According to the docs:
At a minimum, the delegate needs to perform the actual search when text is entered in the text field.
So using any of those methods should meet the minimum requirement
See here for the full docs

Related

Close UIDatePicker after selection when style is .compact

Storyboard application using Swift.
How can I close the calendar (presented for date selection when the UIDatePicker style is .compact) after a user selects a date? Currently, the user is required to tap outside the calendar for it to close. I want it to close automatically after a user has selected a date.
I've tried resigning first responder of the UIDatePicker, ending editing in the table view controller once a notification is received that a date changed, reading another response (here) and so far nothing is working for me.
For now, you can do:
presentedViewController?.dismiss(animated: true, completion: nil)
i.e.
override func viewDidLoad() {
super.viewDidLoad()
datePicker.addTarget(self, action: #selector(dateChanged), for: .valueChanged)
}
#objc private func dateChanged() {
presentedViewController?.dismiss(animated: true, completion: nil)
}
But note that this will potentially break in future iOS versions as this relies on the implementation detail that compact date pickers presents a view controller to show the calendar. If you print out the type of the presented VC, you'll see _UIDatePickerIOSCompactViewController. The leading _ suggests that this is an implementation detail.
All that is documented about compact is:
A style indicating that the date picker displays as a label that when tapped displays a calendar-style editor.
So in future versions of iOS, they could instead change the implementation to not present a new view controller, and this could very well break, and you'll have to find another way then. I see no documented way of doing this right now.

Prevent VoiceOver From Reading UIButton as "Possible Text"

I am writing a very simple game that is designed specifically for blind users, but may also be used by sighted users. It uses many buttons as elements, however, blind users interact with these buttons through custom gestures (pan, tap, etc), so standard voiceover interaction is not appropriate.
The issue lies in the fact that there are no accessibility objects on the screen at all, so whenever the game loads, voiceover starts reading the labels on buttons (e.g. "Possible text: back, menu...). These buttons are read regardless of the fact that they are not enabled. I also can't remove most of them from the view for blind users.
I have tried turning off accessibility for the elements, unchecking "button" from accessibility traits, everything has allows direct interaction selected, I have tried .accessibilityElementsHidden, all the suggestions from How do you exclude a UIButton from VoiceOver? and nothing seems to work.
My current solution has a clear UILabel with no text in it, this is set to the only item in the .accessibilityElements array, and then for good measure I post an accessibility screen changed notification with that label as the object so it becomes focused, then I wait a second in a dispatch queue async after call, remove the label entirely, and set focus back to the main view so the user can interact.
Here is an example of my current solution:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.accessibilityElements = [lblVoiceOver!]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIAccessibility.post(notification: .screenChanged, argument: lblVoiceOver)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.lblVoiceOver.removeFromSuperview()
UIAccessibility.post(notification: .screenChanged, argument: self.view)
}
}
This is a silly hack, at best, and I would love to implement a real solution that simply prevents the "Possible text" from being read by voiceover. I believe the possible text feature was added in iOS 11, to help apps that are not written with accessibility in mind to be more accessibility friendly, but so far I haven't found a way to turn this off.
The issue lies in the fact that there are no accessibility objects on the screen at all.
If you want to reach this purpose, just write self.view.accessibilityElementsHidden = true in your view controller that will contain no accessible element for VoiceOver anymore: this will indicate that the content of your container isn't accessible.
blind users interact with these buttons through custom gestures (pan, tap, etc), so standard voiceover interaction is not appropriate [...] I would love to implement a real solution that simply prevents the "Possible text" from being read by voiceover.
... following the preceding rationale, you should prevent VoiceOver from analyzing and reading anything in your view.
Now, dealing just with your buttons, I created a blank project with a simple code for the view controller hereafter (Swift 5.0, iOS 12):
import UIKit
class NonAccessibleButtonVC: UIViewController {
#IBOutlet weak var aboveLabel: UILabel!
#IBOutlet weak var belowLabel: UILabel!
#IBOutlet weak var myButton: UIButton!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
myButton.accessibilityElementsHidden = true
}
}
... and you get the following result on your device:
The button isn't taken into account as an accessible element and if it doesn't work in your code, it means that anything else prevents this correct operation. Add maybe the setAccessibleElement button property value to false according to your context ?
However, another solution could be defining the display of the desired accessibility objects in your view thanks to its accessibilityElements property taking away the buttons for instance (see Example 2 of this link): that will definitely work in addition to ordering all your elements.

swift update textview after typing new text

I'm making an app in swift 3
I have a TableViewController, when I click on a row it opens a ViewController in which there is a TextView. When I click on it, I am able to edit it, but the changes are not "saved". when I go back to my list and re-click on the same row, the text is back to "Default", I've been on this problem all day, and I don't know how to solve it, I've tried solution from stackoverflow but only give solution to change the text colours.
So how can I do that ?
Based on what you've said so far, I can only assume that you are not storing the new text that's been entered into your UITextView.
The reason that the changes are not being saved is that every time you "open" a new ViewController by tapping on a TableViewCell, it is a brand new instance of that ViewController. That means that it has no input as far as what should be displayed in the TextView, and therefore displays the default text.
But how do I fix it?
This is going to require that you save your TextView's text every time that you leave or dismiss the ViewController. This is because when you leave the ViewController, it is being released from memory. This means that anything that was written there no longer exists. I would suggest storing the text in an array of strings on your TableViewController, which should allow for the text to persist over the life cycle of the app.
The text disappears when I close the app!
This is something that will require data persistence, and for that I would suggest reading up on how to use Core Data to store and persist data across multiple app life cycles.
There are many ways to achieve that, the idea is to set the text of the text view in its view controller's initialization.
So, you need a place to save the data entered by the user, and then read that data again in the view controller's initialization.
One way to do that is using a singleton class to save the data, like this for example:
class SharedData {
static let instance = SharedData()
var textEnteredByUser: String?
}
Now, when the user enters text, save that text in the string we have in 'SharedData' like this:
SharedData.instance.textEnteredByUser = textView.text!
Now, we have the data entered by the user saved, we need to set the data in the text view before it appears in the screen, and this can be achieved inside viewDidLoad method of your view controller like this:
override func viewDidLoad() {
super.viewDidLoad()
textView.text = SharedData.instance.textEnteredByUser
}
You can also save your data in user defaults instead of the singleton class like this:
UserDefaults.standard.set(textView.text!, forKey: "textEnteredByUser")
And then retrieve the saved text this way:
UserDefaults.standard.string(forKey: "textEnteredByUser")
Note: The singleton solution keeps the data shared between different view controllers, but does not persist data after closing the application, and this is what user default does.

How can I close a Safari App Extension popover programmatically?

I'm building a Safari App Extension using XCode 8.3 and Swift 3, following the Safari App Extension Programming Guide. The extension includes a popover that appears when the extension's toolbar item is clicked. The popover view contains a few buttons linked to actions the user can perform.
I want clicking one of these buttons to close the popover after its action has been performed. By default, clicking anywhere outside of a popover closes it, but I haven't been able to find any other way to close the popover, either in the guide or in the docs.
I know that NSPopover has a performClose method, but there doesn't appear to be a way to access the popover itself from within the extension: the app extension only lets you provide a SFSafariExtensionViewController, whose contents magically appear within the popover.
I've also tried using dismissViewController as described in this StackOverflow answer, but in my view controller self.presenting is always nil, and self.dismissViewController(self) just crashes the extension with the message:
dismissViewController:: Error: maybe this view controller was not presented?.
Lastly, I noticed a related question about programmatically opening the toolbar item popover has gone unanswered the past 6 months. This leads me to suspect Apple may simply have strict limits on how the popover can be opened and closed. Even if this is the case, it would be nice to know for sure what the limitations are.
I'll add an answer in case anyone stumbles upon this question.
A dissmissPopover() instance method has been added to the SFSafariExtensionViewController class. This can be used to programatically close the popover.
The default template given when creating a Safari App Extension in XCode gives you a SafariExtensionViewController class that extends SFSafariExtensionViewController and holds a shared instance as a static field called 'shared', so you can call the dismissPopover() method from that instance.
For example:
class SafariExtensionHandler: SFSafariExtensionHandler {
func myFunc() {
// do stuff;
SafariExtensionViewController.shared.dismissPopover()
// do other stuff;
}
}
I did it by calling dismiss method like below
#IBAction func onLoginBtnClicked (_ sender: Any) {
NSLog("Button clicked")
self.dismiss(self)
}

Need to use a NSTextView (or NSTextField) for a clickable URL

I'm given an arbitrary NSAttributedString (parsed from markdown, not that it matters here) which may contain URLs that I want to be clickable in a text field within an NSTableView cell. The requirements state that if the user clicks the URL, they be taken to it with the default browser. IF they click anywhere else in the cell, we have default behavior (displaying an additional info popup).
I'm attempting to use a NSTextView to display the content. However, clicking outside the URL but within the view selects the text and eats the mouse click. Making the view not selectable won't allow clicking the URL either. I also don't really want the text to be selectable but that's a minor side problem.
So... I decided to make my view controller an NSTextViewDelegate so I could use some of those callbacks. But my app crashes if I set the NSTextView's delegate property to 'self'. This happens even if I don't implement any of the functions, even though they are all optional.
I'm using Swift 3 and wonder if there's some bug or other issue there? The call stack appears to be sending a textView:willChangeSelectionFromCharacterRanges:toCharacterRanges: message even though it's not implemented. And incidentally, implementing that method isn't helping either.
Any help, or sample code in Swift 3 using the delegate protocol, would be greatly appreciated.
Here's the crash I get by simply setting the delegate property on NSTextView:
By request, here's the code that set's the delegate. Currently I just set it whenever the message changes. This can obviously be optimized but for now I just want to see it work.
var notification: SSNotification! {
didSet {
guard let notificationCellView = self.view as? SSNotificationCellView else { return }
notificationCellView.subjectLabel.stringValue = notification.subject
if let description = notification.message , description != "" {
let attrString = TSMarkdownParser.standard().attributedString(fromMarkdown: description)
notificationCellView.messageLabel.textStorage?.setAttributedString(attrString)
notificationCellView.messageLabel.isHidden = false
notificationCellView.messageLabel.delegate = self
} else {
notificationCellView.messageLabel.isHidden = true
}
}
}
I never did figure out why I was crashing but I was able to come up with a workaround. I was originally trying to make the view controller for the table cell which contained NSTextView be the delegate. I changed it so that the cell's view subclass itself was the delegate and all is well.
I don't get it but it works, so that's what matters.