swift calling a func from inside static func? - swift

I am trying to call upon func to bring up an alert and it is inside another static func though it's not working
inside my fetch user func when actual longitude return nil i want to call the alert func though its not working as i thought it would
it says error: extra argument in call "message
func alertTheUser(title: String , message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil);
alert.addAction(ok);
present(alert, animated: true, completion: nil);
}
static func firestorefetchUserWithUID(uid: String, completion:#escaping (User2) -> ()) {
//code taken out for this example
//fetch user from database
//dictionary = snapshot etc
guard let latitude = dictionary["Actual Latitude"] as? String else {
alertTheUser(title:"title" , message:"message")
return
}
//code taken out for this example
}

A static func cannot call a non-static func directly. A non-static func is an instance method, and in a static func there is no instance — it is static, meaning it belongs to the type, not to an instance of the type.
Thus, static firestorefetchUserWithUID cannot call alertTheUser because alertTheUser is an instance method and you have no instance to send it to. And if alertTheUser were static, you'd have the same problem again, because then it could not call present for the same reason, as present is an instance method.
It looks to me like making the static func static was just a mistake to begin with; make it an instance method, if you know you'll always have an instance to send it to. (And I presume you do have an instance, because your use of present suggests that this code must be in a UIViewController subclass.)

Related

Swift How to pass argument to function #selector [duplicate]

I'm programmatically adding a UITapGestureRecognizer to one of my views:
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))
self.imageView.addGestureRecognizer(gesture)
func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
The first problem I encountered was "Argument of '#selector' does not refer to an '#Objc' method, property, or initializer.
Cool, so I added #objc to the handleTap signature:
#objc func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
Now I'm getting the error "Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C.
It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.
It looks like you're misunderstanding a couple of things.
When using target/action, the function signature has to have a certain form…
func doSomething()
or
func doSomething(sender: Any)
or
func doSomething(sender: Any, forEvent event: UIEvent)
where…
The sender parameter is the control object sending the action message.
In your case, the sender is the UITapGestureRecognizer
Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…
func handleTap(sender: UIGestureRecognizer) {
}
you should have…
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap
Step 1: create the custom object of the sender.
step 2: add properties you want to change in that a custom object of the sender
step 3: typecast the sender in receiving function to a custom object and access those properties
For eg:
on click of the button if you want to send the string or any custom object then
step 1: create
class CustomButton : UIButton {
var name : String = ""
var customObject : Any? = nil
var customObject2 : Any? = nil
convenience init(name: String, object: Any) {
self.init()
self.name = name
self.customObject = object
}
}
step 2-a: set the custom class in the storyboard as well
step 2-b: Create IBOutlet of that button with a custom class as follows
#IBOutlet weak var btnFullRemote: CustomButton!
step 3: add properties you want to change in that a custom object of the sender
btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)
step 4: typecast the sender in receiving function to a custom object and access those properties
#objc public func btnFullRemote(_ sender: Any) {
var name : String = (sender as! CustomButton).name as? String
var customObject : customObject = (sender as! CustomButton).customObject as? customObject
var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2
}
Swift 5.0 iOS 13
I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.
Create a custom class, throw a enum to keep/make the code as maintainable as possible.
enum Vs: String {
case pulse = "pulse"
case precision = "precision"
}
class customTap: UITapGestureRecognizer {
var cutomTag: String?
}
Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.
let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true
And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.
#objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
let tag = customTag as? customTap
switch tag?.sender {
case Vs.pulse.rawValue:
// code
case Vs.precision.rawValue:
// code
default:
break
}
}
And there you have it.
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)
#objc func showAlert(_ sender: UIButton){
print("sender.tag is : \(sender.tag)")// getting tag's value
}
Just create a custom class of UITapGestureRecognizer =>
import UIKit
class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {
let userModel: OtherUserModel
init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
self.userModel = userModel
super.init(target: target, action: action)
}
}
And then create UIImageView extension =>
import UIKit
extension UIImageView {
func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
isUserInteractionEnabled = true
let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
addGestureRecognizer(gestureRecognizer)
}
#objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
}
}
Now use it like =>
self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
You can use an UIAction instead:
self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
self?.handleTap(modelObj)
}, for: .touchUpInside)
that may be a terrible practice but I simply add whatever I want to restore to
button.restorationIdentifier = urlString
and
#objc func openRelatedFact(_ sender: Any) {
if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
}

Returning the result of a UIAlert to a delegate method

Is it possible, with a UIAlert, to return what the user selected to a delegate method? If so, how?
I'd prefer not to change the delegate method or stop using UIAlert, if such a solution exists. Though all help and ideas are appreciated.
Delegate Protocol
protocol RouteManagerDelegate {
//behaves like textFieldShouldEndEditing
func routeShouldUpdateUnfinished() -> Bool
}
My Current Attempt at Implementation
extension MyController : RouteManagerDelegate {
func routeShouldUpdateUnfinished() -> Bool {
var response = false
//make Alert
let confirmationAlert = UIAlertController(title: "Current Route is Unfinished", message: "Do you want to continue?", preferredStyle: .alert)
//add Yes or No options
confirmationAlert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in
response = true
})
confirmationAlert.addAction(UIAlertAction(title: "No", style: .cancel)
self.present(confirmationAlert, animated: true)
return response
}
}
This doesn't work because UIAlert behaves asynchronously. The function simply returns false every time.
But you can clearly see the intent:
If the user selects 'Yes' (continue), then routeShouldUpdateUnfinished should return true.
For me the provided code looks like the delegate method is being understood wrong. To help you out a little, here a little diagram:
The delegate pattern is used to communicate from child to parent.
In your solution this could look like:
Parent implements delegate pattern with function
func routeShouldUpdateUnfinished(result: bool) { ... }
When parent sets child up the parent calls
child.delegate = self
The alert inside child calls
delegate.routeShouldUpdateUnfinished(true)
The parent handles the code
Just in case someone finds themselves in this esoteric position:
I wanted to do as Ramden suggested, but also didn't want to expose a function that other classes should never use (except in this scenario).
I ended up using a default implementation for my delegate method (defined in an extension of my protocol) and a static fileprivate method to handle the result of my delegate method returning true.
Now the fileprivate 'handler' method isn't exposed but I can also use a UIAlert. This has flaws but works. Implementation below, if ever it's useful to someone.
protocol RouteManagerDelegate {
func routeShouldUpdateUnfinished() -> Bool
}
extension RouteManagerDelegate { //default implementations of delegate protocol
func routeShouldUpdateUnfinished() -> Bool {
if let delegate = self as? UIViewController {
let alert = UIAlertController(title: "Wait!", message: "Do you want to continue?", preferredStyle: .alert)
confirmationAlert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in
RouteManager.handleRouteChangedUnfinished() //the 'handler' function
})
confirmationAlert.addAction(UIAlertAction(title: "No", style: .cancel))
delegate.present(confirmationAlert, animated: true)
}
return false
}
}
struct RouteManager {
func myFunc() {
...
if delegate?.routeShouldUpdateUnfinished() ?? true {
RouteManager.handleRouteChangedUnfinished() //the 'handler' function
}
...
}
static fileprivate func handleRouteChangedUnfinished() { //don't want to expose this
//Notify the Database
}
}

Swift: How to properly write UIAlertAction with textfield and textview validation?

I am creating an app with one of the tab being a guestbook where users can write something. I use an UIAlertController to pop up a window with a textfield for entering name, and a textview for entering the message. I want to make the "Post" button disabled by default, then enable it after the name field has at least 2 characters and the message has at least 3 characters. I have achieved this by
#1: declare the "Post" UIAlertAction at the top
let saveAction = UIAlertAction(title:"Post", style: .default, handler: { (action) -> Void in print(data) })
The above line gives the error (Cannot use instance member 'data' within property initializer; property initializers run before self is available.)
#2: add this "Post" button in alert and making it disabled
alert.addAction(saveAction)
saveAction.isEnabled = false
#3 add two functions to detect how many words are in the textfield and textview, and if they meet the requirement, enable the "Post" button
func textViewDidChange(_ textView: UITextView) { //Handle the text changes here
GuestbookContentWordCount = textView.text.count
data["content"] = textView.text
enableSave()
}
#objc func textFieldDidChange(_ textField: UITextField) {
GuestbookNameWordCount = textField.text?.count ?? 0
data["name"] = textField.text
enableSave()
}
func enableSave () {
if GuestbookContentWordCount >= 3 && addGuestbookNameWordCount >= 2 {
saveAction.isEnabled = true
} else {
saveAction.isEnabled = false
}
}
The ideal situation is when the requirements are met and the user clicks on the "Post" button, I will get the data["name"] and data["content"] and insert it into a database. Right now I have gotten it to work to the point that the "Post" button is enabled after the requirements are met, but when trying to get the data it gives the error "Cannot use instance member 'data' within property initializer; property initializers run before self is available.
Can you please advise how to solve this problem? Thank you.
So this is what I would do
Change your UIAlertAction reference to this outside the viewDidLoad()
var saveAction: UIAlertAction? = nil
Inside the viewDidLoad() you can instantiate it like:
saveAction = UIAlertAction(title:"Post", style: .default, handler: { [weak self] (action) -> Void in
guard let `self` = self else { return }
print(self.data) }
)
[weak self] is used so that you don't end up having retain cycles after your UIViewController is deinitialised.

How does closures capture data?

I don't understand this concept that a closure captures data.. Can someone write a sample code using closures that show how data never gets destroyed.. I already read the Apple documents and I'm still confused. And also how does 'unowned' and 'weak' make any difference in a closure...
class TableViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()
override func viewDidLoad() {
super.viewDidLoad()
if let allWordsPath = Bundle.main.path(forResource: "start", ofType: "txt"){
if let startWords = try? String(contentsOfFile: allWordsPath){
allWords = startWords.components(separatedBy: "\n")
}else{
allWords = ["Cake"]
}
startGame()
}
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Make Word", style: .plain, target: self, action: #selector (makeWord))
}
func startGame(){
allWords = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: allWords) as! [String]
title = allWords[0]
usedWords.removeAll(keepingCapacity: true)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usedWords.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Word", for: indexPath)
cell.textLabel?.text = usedWords[indexPath.row]
return cell
}
func makeWord() {
let ac = UIAlertController(title: "Add Word", message: nil, preferredStyle: .alert)
ac.addTextField(configurationHandler: nil)
let submit = UIAlertAction(title: "Submit", style: .default){ [unowned self,ac]
(action: UIAlertAction!) in
let answer = ac.textFields?[0]
self.submit(answer: (answer?.text)!)
}
ac.addAction(submit)
present(ac,animated: true)
}
var number = 10
func submit(answer: String){
usedWords.append(answer)
tableView.reloadData()
}
how does unowned work here if we are not explicitly deallocating things..
You should first search for the difference between strong, weak, and unowned. There are PLENTY of answers here on SO about it.
Anyways, in this specific case:
Your closure has this code:
[unowned self,ac]
This is called a "capture list". It indicates the things that should be "captured" by value WHEN THE BLOCK is created. (if you don't specify them here, and you change the value somewhere after the block, the value inside the block would also be changed).
The reason why self is unowned and doesn't require to be deallocated is because unowned means:
"Don't worry about memory management for this variable, it will ALWAYS
have a value for the duration of my closure"
So, going back to unowned self, the reason you should declare weak or unowned the self variable from a closure is because if not you would create a retain cycle. Things can't be deallocated as long as something is referencing them. So in this case, your TableViewController is keeping your closure alive, and your closure is keeping your TableViewController alive. So because they are referencing each other none of them can get deallocated properly. -> Memory Leak
So we can conclude that self has to either be weak or unowned. For all intents and purposes in this example they are exactly the same. They both serve the purpose of "breaking the retain cycle" by removing the ability of the closure to keep self alive. So who will dealloc self you ask? your closure doesn't care. But think outside your closure. Your closure is being called by your TableViewController, so since there's no weird things going on here we can safely assume that, if there is an alert being shown it must definitively be shown over your TableViewController. So once you dismiss the alert or whatever, your TableViewController will keep working as usual. One you dismiss your TableViewController though, self WILL be deallocated (since the closure is referencing it as unowned), but by this point there is no way the alert is being shown. If you however do some weird things that make your TableViewController be dismissed WHILE the alert is still being shown, then once the user "submits" your app will crash. BECAUSE by declaring your variable unowned you basically made a promise to your closure that it wouldn't have to worry about the self entity, as it would always exist as long as your closure was alive.
Check this out. I'm creating 2 objects of the same kind. One has a reference to a closure that retains itself, so even if the function that created it goes out of scope, the object and the closure retain each other and never get released. The second object's closure has a weak reference to the object, and so when the object creating function goes out of scope, the reference count is 0, and when it gets released it releases the closure as well.
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class B {
deinit {
print("\(name) deinit")
}
var name: String
init(name: String) {
self.name = name
}
var zort: (() -> ())?
func someMethod() {
print("")
}
}
func createStuffThatNeverGoesAway() {
var b: B = B(name: "bad");
b.zort = {
b.someMethod()
}
}
func createStuffThatGoesAway() {
var b: B = B(name: "good");
b.zort = { [weak b] in
b?.someMethod()
}
}
createStuffThatNeverGoesAway()
createStuffThatGoesAway()
Output:
good deinit

Trigger UIAlertAction on UIAlertController programmatically?

There are a couple of existing questions on this topic but they aren't quite what I'm after. I've written a little Swift app rating prompt for my app which presents two UIAlertController instances, one triggered by the other.
I'm now trying to unit test this, and trying to reach that second alert in the tests. I've written a simple spy to check the first controller, but I'd like a way to trigger one of the actions on the first alert, which in turn shows the second.
I've already tried alert.actions.first?.accessibilityActivate(), but it didn't seem to break inside the handler of that action – that's what I'm after.
A solution that doesn't involve changing the production code to allow programmatic tapping of UIAlertActions in unit tests, which I found in this SO answer.
Posting it here as well as this question popped up for me when Googling for an answer, and the following solution took me way more time to find.
Put below extension in your test target:
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
guard let block = actions[index].value(forKey: "handler") else { return }
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(actions[index])
}
}
Here's roughly what I did:
Created a mocked version of my class that would present the alert controller, and in my unit tests, used this mock.
Overrode the following method that I'd created in the non-mocked version:
func alertActionWithTitle(title: String?, style: UIAlertActionStyle, handler: Handler) -> UIAlertAction
In the overridden implementation, stored all the details about the actions in some properties (Handler is just a typealias'd () -> (UIAlertAction))
var didCreateAlert = false
var createdTitles: [String?] = []
var createdStyles: [UIAlertActionStyle?] = []
var createdHandlers: [Handler?] = []
var createdActions: [UIAlertAction?] = []
Then, when running my tests, to traverse the path through the alerts, I implemented a callHandlerAtIndex method to iterate through my handlers and execute the right one.
This means that my tests look something like this:
feedback.start()
feedback.callHandlerAtIndex(1) // First alert, second action
feedback.callHandlerAtIndex(2) // Second alert, third action
XCTAssertTrue(mockMailer.didCallMail)
I took a slightly different approach based on a tactic I took for testing UIContextualAction—it's very similar to UIAction but exposes its handler as a property (not sure why Apple wouldn't have done the same for UIAction). I injected an alert actions provider (encapsulated by a protocol) into my view controller. In production code, the former just vends the actions. In unit tests, I use a subclass of this provider which stores the action and the handler in two dictionaries—these can be queried and then triggered in tests.
typealias UIAlertActionHandler = (UIAlertAction) -> Void
protocol UIAlertActionProviderType {
func makeAlertAction(type: UIAlertActionProvider.ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction
}
Concrete object (has typed titles for easy retrieval later):
class UIAlertActionProvider: UIAlertActionProviderType {
enum ActionTitle: String {
case proceed = "Proceed"
case cancel = "Cancel"
}
func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
let style: UIAlertAction.Style
switch title {
case .proceed: style = .destructive
case .cancel: style = .cancel
}
return UIAlertAction(title: title.rawValue, style: style, handler: handler)
}
}
Unit testing subclass (stores actions and handlers keyed by ActionTitle enum):
class MockUIAlertActionProvider: UIAlertActionProvider {
var handlers: [ActionTitle: UIAlertActionHandler] = [:]
var actions: [ActionTitle: UIAlertAction] = [:]
override func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
handlers[title] = handler
let action = super.makeAlertAction(title: title, handler: handler)
actions[title] = action
return action
}
}
Extension on UIAlertAction to enable typed action title lookup in tests:
extension UIAlertAction {
var typedTitle: UIAlertActionProvider.ActionTitle? {
guard let title = title else { return nil }
return UIAlertActionProvider.ActionTitle(rawValue: title)
}
}
Sample test demonstrating usage:
func testDeleteHandlerActionSideEffectTakesPlace() throws {
let alertActionProvider = MockUIAlertActionProvider()
let sut = MyViewController(alertActionProvider: alertActionProvider)
// Do whatever you need to do to get alert presented, then retrieve action and handler
let action = try XCTUnwrap(alertActionProvider.actions[.proceed])
let handler = try XCTUnwrap(alertActionProvider.handlers[.proceed])
handler(action)
// Assert whatever side effects are triggered in your code by triggering handler
}
I used Luke's guidance above to create a subclass of UIAlertAction that saves its completion block so it can be called during tests:
class BSAlertAction: UIAlertAction {
var completionHandler: ((UIAlertAction) -> Swift.Void)?
class func handlerSavingAlertAction(title: String?,
style: UIAlertActionStyle,
completionHandler: #escaping ((UIAlertAction) -> Swift.Void)) -> BSAlertAction {
let alertAction = self.init(title: title, style: style, handler: completionHandler)
alertAction.completionHandler = completionHandler
return alertAction
}
}
You could customize this to save more information (like the title and the style) if you like. Here's an example of an XCTest that then uses this implementation:
func testThatMyMethodGetsCalled() {
if let alert = self.viewController?.presentedViewController as? UIAlertController,
let action = alert.actions[0] as? BSAlertAction,
let handler = action.completionHandler {
handler(action)
let calledMyMethod = self.presenter?.callTrace.contains(.myMethod) ?? false
XCTAssertTrue(calledMyMethod)
} else {
XCTFail("Got wrong kind of alert when verifying that my method got called“)
}
}