I don't post the entire code because it's really too large. Anyway, it's a user registration built this way.
There is a modal whose child is a pageViewController:
the first page is for Login;
the second page is for Registration;
From Login you can reach Registration and, once registered, pageViewController should automatically transition to the first page, so that the user can login with his new credentials.
The process of user registration is managed by a button and Alamofire: once the button is clicked, the values the user inserted in the textfields are validated, then I start a post request, sending data to the server and receiving JSON data back in a while.
It's anything very simple (sorry for speaking too much), but in the end, after I receive the JSON, something strange happens here:
let j = JSON as! NSDictionary
let userStatus = j.object(forKey: "status")!
if((userStatus as! Int) == 0){
//
print("user registerd")
let alert = UIAlertController(title: "Registration ok", message: "Registration OK", preferredStyle:UIAlertControllerStyle.alert)
let okAlert = UIAlertAction(title:"OK", style: .default, handler: {action in v.scrollToPreviousViewController() })
alert.addAction(okAlert)
self.present(alert, animated: true, completion: nil)
What is supposed to do this code? Once the user clicks the alert button, the pageviewcontroller should return with an animation to the login screen.
What happens? It returns back there but without animation.
This led me to think I should avoid "polluting" the global thread reserved to the GUI. And, in fact, I tried to put all the code inside a:
DispatchQueue.main.async { [unowned self] in
without any success.
Then, I tried another thing:
let okAlert = UIAlertAction(title:"OK", style: .default, handler:
{action in DispatchQueue.main.async { [unowned self] in v.scrollToPreviousViewController() }})
I don't perfectly understand why, but this practically works and eliminates the issue with the animation.
But that unowned self there generates a warning: "Capture self was never used". What am I doing wrong?
If you didn't use self inside the async block then you will get this warning obviously. Please try with _ instead of self.
to see what is swift capture list for, check this example ...
var i = 25
var cl = {print(i)}
var clc = {[i] in print(i)}
i = 35
cl() // 35
clc() // 25
the warning message "Capture self was never used" is therefore self-explanatory. you don't need to put self in your capture list, because you don't use it there
Related
How do I filter the images shown on the PHPickerViewController to those that have been selected under limited access by the user? Or do I need to use a different picker? I've been struggling with this for a few days now. Any help would be appreciated. Thanks in advance.
When user taps the button:
1-5 are automatic
The alert appears with Camera or Photo Library
They choose Photo Library
The authorization alert appears with Select Photo…, Allow Access to All Photos or Don’t Allow
They tap Select Photos = .limited
The presentLimitedLibraryPicker is displayed, for the user to choose the photos thay want to allow and taps Done.
Now I want the picker to appear with a filtered choice of the images the user has just chosen. Seems like this would be automatic too. Not...
This only displays the same picker where the user made the selections for limited access.
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self)
What goes in the .limited case?
var config = PHPickerConfiguration()
config.selectionLimit = 1
config.filter = PHPickerFilter.any(of: [.images, .livePhotos])
let picker_Photo = PHPickerViewController(configuration: config)
picker_Photo.delegate = self
let libCell = UIAction(title: "Photo Library", image: UIImage(systemName: "photo"), identifier: .none, discoverabilityTitle: .none) { (libAction) in
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary)
{
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
switch status
{
case .limited:
DispatchQueue.main.async
{
// PHPhotoLibrary.shared().register(self)
// PHPhotoLibrary.shared().presentLimitedLibraryPicker
self.present(picker_Photo, animated: true, completion: nil)
}
case .authorized:
DispatchQueue.main.async
{
self.present(picker_Photo, animated: true, completion: nil)
}
case .notDetermined:
DispatchQueue.main.async
{
self.present(picker_Photo, animated: true, completion: nil)
}
case .restricted:
self.view.sendConfirmationAlert(theTitle: "Photo Library Restricted",
theMessage: "Photo Library access was previously denied. Please update your Settings to allow access.", buttonTitle: "Ok")
case .denied:
let settingsAlert = UIAlertController(title: "Photo Library Access Denied",
message: "Photo Library access was previously denied. Please update your Settings to allow access.", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Go to Settings", style: .default) { ( action ) in
let settingsUrl = URL(string: UIApplication.openSettingsURLString)
UIApplication.shared.open(settingsUrl!, options: [:])
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
DispatchQueue.main.async
{
settingsAlert.addAction(settingsAction)
settingsAlert.addAction(cancelAction)
self.present(settingsAlert, animated: true)
}
default:
return
}
}
}
}
You are mistaken about what limited access means. It restricts what information you can read and write into the photo library.
But your code does not read from the photo library in the first place. You are saying
config = PHPickerConfiguration()
So this means you have no photo library access at all by way of the picker. You can only get image copies. You therefore do not need any kind of permission to show the picker, and the user limitation makes no difference to you. You can in fact just throw away all your authorization related code; it has no effect. Your picker will work exactly the same without it.
If you needed photo library access by way of the picker, you would have called
https://developer.apple.com/documentation/photokit/phpickerconfiguration/3616114-init
In that case the user limitation would matter to you — when you tried to access an actual PHAsset. But your code never does that. You are asking for a permission you do not need.
I'm working with a simple button that, when pressed, runs a function I created.
Originally, I wrote the code like this:
func askQuestion() {
// runs code to ask a question
}
#IBAction func buttonTapped(_ sender: UIButton) {
let ac = UIAlertController(title: title, message: "You tapped the button.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Continue", style: .default, handler: askQuestion))
present(ac, animated: true)
But that returns an error:
Cannot convert value of type '() -> ()' to expected argument type '((UIAlertAction) -> Void)?'
Which is fixed when you add the following parameter to askQuestion():
askQuestion(action: UIAlertAction! = nil)
Why does a handler method passed into the UIAlertAction require that it accept a UIAlertAction parameter? It seems like an unnecessary step, but I was thinking it might be a way to scan your code for hints that this function is triggered by a button.
You could have a single handler responsible for handling multiple actions
var continueAction: UIAlertAction!
var cancelAction: UIAlertAction!
override func viewDidLoad() {
super.viewDidLoad()
continueAction = UIAlertAction(title: "Continue", style: .default, handler: masterHandler)
cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: masterHandler)
}
//...
let ac = UIAlertController(title: title, message: "You tapped the button.", preferredStyle: .alert)
ac.addAction(continueAction)
ac.addAction(cancelAction)
Personally, I don't know why you might do this, but the API designers felt it was a good idea to provide you with the flexibility to design your solution which best meet your needs.
So, in most cases, while I appreciate it seems weird (especially when you can use closures), I do appreciate having the information available to make my own choices
What if the alert has a text field in it and you need to fetch that text?
What if the same function is shared by multiple alerts and you need to know which alert this is (perfectly possible)?
What if this is a UIAlertController subclass with a property or method you need to access? (Yes I’ve done that.)
You need a reference to the alert! And you need it as an already weak reference so you don’t get a retain cycle if you’re using a trailing closure. So the action handler is given one as parameter.
Trust me, if you didn’t get a reference to the alert, sooner or later you’d find yourself in a situation where you’d be furious and stymied.
You need
func askQuestion(_ action:UIAlertAction){} // ->() is the void return here
necessary/not UIAlertAction handler has UIAlertAction -> () callback so you have to implement , sometimes you do
alert.addAction(UIAlertAction.......{ (action) in }
so it's useful to know some details about the current action like title or style
I have done abit of research on stackoverflow and apple's documentation about ARC and Weak/Unowned self (Shall we always use [unowned self] inside closure in Swift). I get the basic idea about strong reference cycle and how it is not good as they cause memory leaks. However, I am trying to get my head around when to use Weak/Unowned self in closures. Rather then going into the "theory", I think it would really really help if someone could kindly explain them in terms of the bottom three cases that I have. My questions is
Is it OK to put weak self in all of them (I think for case two there is no need because I saw somewhere that UIView is not associated with self?. However, what if I put weak self there, is there anything that can cause me headache?
Say if the answer is No, you cant put weak self everywhere in all three cases, what would happen if I do (example response would be much appreciated...For example, the program will crash when this VC ....
This is how I am planning to use weakSelf
Outside the closure, I put weak var weakSelf = self
Then replace all self in closure with weakSelf?
Is that OK to do?
Case 1:
FIRAuth.auth()?.signInWithCredential(credential, completion: { (user: FIRUser?, error: NSError?) in
self.activityIndicatorEnd()
self.performSegueWithIdentifier(SEGUE_DISCOVER_VC, sender: self)
})
Case 2:
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.1, animations: {
self.messageLbl.alpha = 0.5
})
Case 3:
//checkUserLoggedIn sends a request to firebase and waits for a response to see if the user is still authorised
checkUserLoggedIn { (success) in
if success == false {
// We should go back to login VC automatically
} else {
self.discoverTableView.delegate = self
self.discoverTableView.dataSource = self
// Create dropdown menu
let menuView = BTNavigationDropdownMenu(navigationController: self.navigationController, title: self.dropDownItems.first!, items: self.dropDownItems)
menuView.didSelectItemAtIndexHandler = {[weak self] (indexPath: Int) -> () in
if indexPath == 0 {
self?.mode = .Closest
self?.sortByDistance()
} else if indexPath == 1 {
self?.mode = .Popular
self?.sortByPopularity()
} else if indexPath == 2 {
self?.mode = .MyPosts
self?.loadMyPosts()
} else {
print("Shouldnt get here saoihasiof")
}
}
// Xib
let nib = UINib(nibName: "TableSectionHeader", bundle: nil)
self.xibRef = nib.instantiateWithOwner(self, options: nil)[0] as? TableSectionHeader
self.discoverTableView.registerNib(nib, forHeaderFooterViewReuseIdentifier: "TableSectionHeader")
// Set location Manager data
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
// Check location service status
if self.locationAuthStatus == CLAuthorizationStatus.AuthorizedWhenInUse {
// Already authorised
self.displayMessage.hidden = false
} else if self.locationAuthStatus == CLAuthorizationStatus.NotDetermined {
// Have not asked for location service before
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("LocationVC") as! LocationVC
vc.locationVCDelegate = self
self.presentViewController(vc, animated: true, completion: nil)
} else {
let alertController = UIAlertController(title: "Enable Location", message: "location is required to load nearby posts", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: nil)
let settingsAction = UIAlertAction(title: "Settings", style: .Default, handler: { (action: UIAlertAction) in
let settingsUrl = NSURL(string: UIApplicationOpenSettingsURLString)
if let url = settingsUrl {
UIApplication.sharedApplication().openURL(url)
}
})
alertController.addAction(settingsAction)
alertController.addAction(cancelAction)
self.presentViewController(alertController, animated: true, completion: nil)
self.displayMessage.hidden = false
self.displayMessage.text = "Could not determine your location to find nearby posts. Please enable location Service from settings"
}
// Styling
self.refreshBtn.tintColor = COLOR_NAVIGATION_BUTTONS
self.discoverTableView.backgroundColor = COLOR_DISCOVERVC_TABLEVIEW_BACKGROUND
// Allow navigation bar to hide when scrolling down
self.hidingNavBarManager = HidingNavigationBarManager(viewController: self, scrollView: self.discoverTableView)
// Allow location to start updating as soon as we have permission
self.locationManager.startUpdatingLocation()
}
}
--Update--
Most of my code looks like case 3 where everything is wrapped inside a closure that either check if there is internet connectivity before any of the action is taken place. So I might have weak self everywhere??
--Update 2--
Case 4:
// The haveInternetConnectivity function checks to see if we can reach google within 20 seconds and return true if we can
haveInternetConnectivity { (success) in
if success == false {
self.dismissViewControllerAnimated()
} else {
self.label.text = "You are logged in"
self.performSegueWithIdentifier("GoToNextVC")
}
}
Question about case 4.
Am I correct to say that even though this closure does not have weak/unowned self, it will never create strong reference (and memory leak) because even if the VC is dismissed before the completion block is executed, Xcode will try to run the code inside the completion block when we have confirmed internet status and will just do nothing (No crash) because self doesn't exist anymore. And once the code reached the last line inside the closure, the strong reference to self would be destroyed hence deallocate the VC?
So putting [weak Self] in that case would just mean that xcode would ignore those lines (as oppose to try and run it and nothing happens) which would mean a better practice but no issues on my hand either way
The question should not be "can I use weak reference," but rather "should I use weak reference." You use weak references to avoid strong reference cycles or to keep a closure from hanging on to something after it may have been disposed. But don't just add weak references because you can.
In case 1, you probably do want to use [weak self]. Why? Because if the view controller was dismissed while the authorization was underway, do you really want to keep a reference to a view controller that was dismissed? Probably not in this case.
In case 2, you theoretically could use [weak self] in the animation block, but why would you? There's no reason to. The weak reference is something you do with completion handlers and/or closure variables, but for an animation block it offers no utility, so I wouldn't do it there. To use weak here suggests a misunderstanding of the memory semantics involved.
In case 3, you have two separate issues.
In the didSelectItemAtIndexHandler, that probably should use [unowned self] because the object's own closure is referring to itself.
It may be a moot issue, as I don't see you actually using that BTNavigationDropdownMenu (perhaps that initializer is adding itself to the navigation controller, but that's not a well designed initializer if so, IMHO).
But as a general concept, when an object has a handler closure that can only be called when the object is still around, but shouldn't, itself, cause the object to be retained, you'd use [unowned self].
In the broader checkUserLoggedIn closure, the question is whether that's a completion handler. If so, you probably should use [weak self], because this could be initiated and be running by the time self is dismissed, and you don't want to have checkUserLoggedIn keep a reference to a view controller that was dismissed. But you wouldn't want to use [unowned self] because that would leave you with dangling pointers if it had been released by the time the closure runs.
As an aside, you contemplate:
weak var weakSelf = self
That is a bit unswifty. You would use the [weak self] pattern at the start of the checkUserLoggedIn closure. If you have an example where you're tempted to use weak var weakSelf = ..., you should edit your question, including an example of where you want to use that pattern. But this is not one of those cases.
I'm using Alamofire for network request. When I load a new viewController I make a new request in ViewDidAppear to get example url to images ect. When I make the request in ViewDidAppear there is a delay before the data appear, I also tried in ViewDidLoad the request was a little bit faster, but you can stil see the data appear after a small delay. It is okay that when a user access the viewController first time the user will see the data is loading, but is there a way to keep the data so that when a user navigate away from the controller, example when a user go back from a push in a navigationController and then navigate forward again without making the request to get the data again?
Here is one of my request in ViewDidAppear.
Hope you guys can help - Thank you
override func viewDidAppear(animated: Bool) {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
var parameters = [String: AnyObject]()
if(self.MySelf) {
parameters = ["userId": LoginViewController.CurrentUser.UserID as AnyObject]
}
else {
parameters = ["userId": self.UserID as AnyObject]
}
//GET posts
Alamofire.request(.POST, Config.ApiURL + "getUserPosts?token=" + LoginViewController.CurrentUser.Token, parameters: parameters as! [String : AnyObject]).responseJSON{ response in
print(response)
switch response.result {
case .Success(let data):
let json = JSON(data)
if let posts = json["post"]["data"].array {
self.postArray = posts
self.postArray = self.postArray.reverse()
self.navigationItem.title = json["user"]["firstname"].string! + " " + json["user"]["lastname"].string!
self.User = json["user"]
self.UserPic = self.User["photourl"].string!
}
else {
print("Array is empty")
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
self.ProfilePostColView?.reloadData()
}
}
Try making the network request in an earlier view controller - say a loading screen and then pass the response to this view controller.
Alternatively you could store the response in a cache service of sorts - when the user navigates back to this view controller you could check it already in the cache if so load it up to the view if not call the request.
Also making the network request in viewDidLoad will be faster as it called before viewDidAppear - but keep in mind viewDidLoad is only called once for a specific instance of a viewController where as viewDidAppear is called every time that instance is displayed again (eg. if it as the bottom of the navigation stack and the user presses back to it).
Keep the user in mind - you do not want to be chewing up their data so if you know the request will have the same response you only want to make the http request once.
so I have the following code in the viewDidAppear section
let theAlert = UIAlertController(title: "SUP", message: "DAWG", preferredStyle: UIAlertControllerStyle.Alert)
theAlert.addAction(UIAlertAction(title: "sup!", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(theAlert, animated: true, completion: nil)
Don't mind the messages, I just came up with them randomly :3
Okay, so is there anyway for me to ONLY display this message when the app launches? Because when I come back from another controller, this message pops up again.
Set a flag to indicate if the message has shown or not.
// first check to see if the flag is set
if alertShown == false {
// show the alert
alertShown = true
}
For this behavior to persist through launches, and show only on FIRST launch, save to NSUserDefaults.
// when your app loads, check the NSUserDefaults for your saved value
let userDefaults = NSUserDefaults.standardUserDefaults()
let alertShown = userDefaults.valueForKey("alertShown")
if alertShown == nil {
// if the alertShown key is not found, no key has been set.
// show the alert.
userDefaults.setValue(true, forKey: "alertShown")
}
You can handle both of these in the root view controller viewDidLoad.