I am having a real challenge within one of my Swift iOS projects that I just don't seem to be able to find a solution for. I was hoping that someone can both recreate my issue and maybe make suggestions as to a resolution or workaround.
To summarize my issue:
I have 2 view controllers within a storyboard. The first view controller contains 3 buttons and the second view controller simply contains an image view. I created a segue on the first button of the first view controller to the second view controller and when I click the button, it works perfectly.
For my second button, I call the segue programmatically. First I assigned the segue an ID (MySegue) within the properties inspector, and then using a method within my view controller, I call the segue. When I click the second button, it works perfectly.
#IBAction func doSegue(sender: AnyObject) {
self.performSegueWithIdentifier("MySegue", sender: nil)
}
For my third button, I wanted to obtain some web data, and only upon success perform the segue. I am making use of NSURLSession to get the data with the completion handler performing the segue. My issue however is that when I click the third button, the Image View will not display no matter what I try. If I add buttons, labels or anything else, they display fine but not images. Here is my code for the button:
#IBAction func doNSURLSession(sender: AnyObject) {
let requestURL: NSURL = NSURL(string: "https://www.google.com")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
if error == nil {
NSLog("Success!")
self.performSegueWithIdentifier("MySegue", sender: nil)
} else {
NSLog("Fail")
}
}
task.resume()
}
I have tried everything I possibly can to work around this issue. I can perform the segue programmatically within a method and it works fine however if I call this segue, or even call a method which calls this segue from the completion handler of the web request, the image view does not show. Does anyone have any ideas as to what I'm either doing wrong, or anywhere I could look?
I have tried this both in XCode6 and the XCode 7 BETA, both have the same result too...
Thanks in advance for any help.
Regards,
Jon
The dataTaskWithRequest completion handler does not execute on the main thread.
Try calling performSegueWithIdentifier on the main thread:
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("MySegue", sender: nil)
}
Related
I've a table view with navigation controller embedded in. I've added a UIBarButtonItem (add) button. When I click this button it opens a new view where user enters the data and submits it (which makes a web service call) and returns back to the previous view. This navigation happens as shown below,
func addTapped(_ sender:UIBarButtonItem) {
print("Called Add")
let vc = (storyboard?.instantiateViewController( withIdentifier: "newNote")) as! newNoteVC
self.navigationController?.pushViewController(vc, animated: true)
}
And in new view I do following,
#IBAction func saveButton(_ sender: UIButton) {
if (self.noteDescription.text?.isEmpty)! {
print("Enter missing note description")
return
} else {
let desc = self.noteDescription.text
self.uploadNote(noteText: desc!, noteDate: self.dateInMilliseconds)
self.navigationController?.popViewController(animated: true)
}
}
This way a record gets saved and a view gets popped from the navigation controller stack but only thing I don't how to do is refresh the table view data in the parent view (where I might need to make a new http service call to read all the records).
I hope I'm able to explain the issue? Any help is appreciated.
As mentioned in the comments, making a service call just to update the tableview might be a overkill. However, if this is the business scenario which needs to be implemented, you can do the same in
func viewWillAppear
in the parent view controller. You can make the service call in this method and reload the table view with the data.
You would also need to check the overall navigation of the application as making service calls in viewWillAppear method is not a good approach as these gets called everytime the view is shown. For Ex: If coming back from a navigated view then also the method is called.
Ok, So I am rather new to Swift and I am a little confused about what I am trying to do, or if I am going in the wrong direction. (https://github.com/ashleymills/Reachability.swift)
Here is my viewDidLoad method :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
checkConnection()
}
I then have a function with the code in from the Reachability GitHub Project:
func checkConnection() {
//declare this property where it won't go out of scope relative to your listener
let reachability = Reachability()!
reachability.whenReachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
DispatchQueue.main.async {
if reachability.isReachableViaWiFi {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
}
}
reachability.whenUnreachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
DispatchQueue.main.async {
self.dim(direction: .In, alpha: self.dimLevel, speed: self.dimSpeed)
self.performSegue(withIdentifier: "mainToAlertNoInternet", sender: self)
}
}
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}
}
As you can see, when there is no internet, this is the code:
self.dim(direction: .In, alpha: self.dimLevel, speed: self.dimSpeed)
self.performSegue(withIdentifier: "mainToAlertNoInternet", sender: self)
The Dim is taken from (http://www.totem.training/swift-ios-tips-tricks-tutorials-blog/ux-chops-dim-the-lights) the mainToAlertNoInternet loads the next view over the current one with transparence so it is an alert style.
So, the second segue has a view and a button on it. Nothing spectacular this is what is loaded when there is no internet:
That try again button is linked to the Exit of the Segue and runs this function in the First View Controller:
#IBAction func unwindFromSecondary(segue: UIStoryboardSegue) {
dim(direction: .Out, speed: dimSpeed)
checkInternetConnection()
}
I added in the function mainToAlertNoInternet so that when they click try again, it will go back to the first segue and run the test again. However, When I click try again, I get this error:
Warning: Attempt to present on whose view is not in the window hierarchy!
Hopefully I have explained enough what I have set up. Now to the Questions:
1) How can I fix this error?
2) Am I doing this the right way or is there a better way?
This is what I want:
I want to check the internet connection the moment the app loads. If there is no connection I want to display the segue like I have been doing. If the user clicks Try gain, I want it to go back to the first controller and run the check again and if still no connection display the segue like it did initially again. I would like this to be a repeating process until there is internet.
Appreciate all your help. Thanks in advance
Edit:
I have added the function call in the ViewDidAppear method like so:
override func viewDidAppear(_ animated: Bool) {
checkInternetConnection()
}
However, it does not run. Also the DIM in the unwindFromSecondary function does not get called when I do this.
Edit 2:
Just added this line into my viewDidAppear:
print("Appeared")
This gets called initially, but then not again.
Question:
How do I get a function to run once everything has loaded again after the unwindSegue?
Any thoughts?
Update 3
Ok, so I have looked at the answers below:
#MarkP Answer works fine. Thank you for that
However, the answer from #iSee has got me thinking maybe I should be going about this a different way.
I have added a Bounty to this post for a detailed answer that can show me and explain how to achieve the following:
In my app. I need to keep making sure that the internet exists (Maybe a Timer) on any view that loads. I would like it so that like the current way, If there is no internet it will pop up the ViewController with this segue:
performSegue(withIdentifier: "mainToAlertNoInernet", sender: self)
It appears that the App Delegate would be the place but I am unsure how to achieve this.
I am new to iOS Development and thus would appreciate some explanation and teaching.
Thank you for your time. It is greatly appreciated.
I presume you are new to iOS development in general.
The warning has nothing to do with your internet connectivity code.
The Warning you are getting is not an error. It is just that, a warning.
The reason you are getting that is well explained in this link and this
To get rid of that warning, you should not call performSegue from viewDidLoad(refer to the above link for more information.
To perform your network checks, it is advisable to use the AppDelegate (gives you a better control over the flow of the app)
All the best :)
EDIT: Please refer to this link here for more information on this. I could easily repost it here but since it is already answered, you can refer to the above link as to why it happens and how to avoid it.
As #iSee has pointed out with the links. This is because the view has not been added to the view hierarchy so you can't move to it. for this self.dismissViewControllerAnimated is needed:
#IBAction func unwindFromSecondary(segue: UIStoryboardSegue) {
dim(direction: .Out, speed: dimSpeed)
self.dismiss(animated: true) {
self.checkInternetConnection()
}
}
I use this in the app delegate ->
func reachablityCode() {
AFNetworkReachabilityManager.sharedManager()
AFNetworkReachabilityManager.sharedManager().startMonitoring()
AFNetworkReachabilityManager.sharedManager().setReachabilityStatusChangeBlock({(status) in
let defaults = NSUserDefaults.standardUserDefaults()
if status == .NotReachable {
defaults.setBool(false, forKey:REACHABLE_KEY)
}
else {
defaults.setBool(false, forKey: REACHABLE_KEY)
}
defaults.synchronize()
})
}
And then this in the base file ->
func isReachable() -> Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(REACHABLE_KEY)
}
a newbie question :)
I'm trying to use Twiiter Digits for authentication (by phone number) in my (first) iOS app.
It is easier for my to understand how to position a button programatically when it is a button that i create. but this 1 line of code confuses me.
to embed their action button i just need to add this part of code (see documentation):
override func viewDidLoad() {
let digitsButton = DGTAuthenticateButton(authenticationCompletion: { (session, error) in
// Inspect session/error objects
})
self.view.addSubview(digitsButton)
}
My problem is that the creation of this button is automatically and have a completion handler, so when\where exactly do i have the option to position (format) it?
Thanks.
You can always create your own custom button and use the methods of Digits to perform the same actions. For example :
func didTapButton(sender: AnyObject) {
let digits = Digits.sharedInstance()
digits.authenticateWithCompletion { (session, error) in
// Inspect session/error objects
}
}
Also if you want to continue to customize your Digits button and it's View Controllers , you can find more here here.
It might sound strange but it took me some time to realize i can\need to do it after the completion handler (inside the ViewDidLoad...).
Thanks for Letting me know i can use my own button with Digits.
I'm trying to build an app similar to Instagram, and I'm stuck at the comments portion. On the left side of the image, it is a VC that has an embedded container view with UITextField and UIButton at the bottom. The container view is embedded with a UITableView that contains all the user profile image, username and the comment itself.
At first load, it can perfectly grab all comments for that post from server side, and displayed perfectly. However, I am unable to call the segue again using prepareForSegue to update the UITableView. I'm receiving an error:
There are unexpected subviews in the container view. Perhaps the embed
segue has already fired once or a subview was added programmatically?
Below are my codes:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "getCommentSegue"){
if let destination = segue.destinationViewController as? CommentsTVC{
if(!self.commentID.isEmpty){
destination.UpdateCommentRow(self.profileImage, commentID: self.commentID, comment: self.postComment, dateTimePost: self.dateTime)
self.commentID = String()
}
destination.postID = self.postID
}
}
}
And after successfully adding a row to my database in the dispatch_async:
self.performSegueWithIdentifier("getCommentSegue", sender: nil)
I've also noticed that when it append new comment to my existing object that stores all the comments, the count for it is 0. I believe it is taking a new reference for the object. Please help!
embed navigationcontroller to container view and then try. It may solve youar problem i think..!!
Remove your container view's subviews before you call performSegue.
I've browsed through many q&a-s here but almost all of them are way too specific for the use cases of other people.
My situation is more general and sort of simple:
iOS\Swift
I have a button which when clicked - moves the user to the next view.
At the same time as the button is clicked it also executes a query to Parse to fetch the data which will be displayed on the next view.
I'm using Parse's async query.getObjectInBackgroundWithId("jjjkkkdddd")
So if my code runs as is = click -> move to the next view -> empty
because fetching stuff takes a second or so.
What i want is to have a small animation popping up when user clicks a button to tell them that the data is being fetched at the moment and move to the next view once data arrives.
Here is my button tap code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "1") {
var svc = segue.destinationViewController as ViewControllerQuotes;
svc.toPass = functionToFetchDataFromParse() //<- takes longer than
//switching to the next view
//toPass is a var which lands into the next view
//and it's value is displayed to the user.
}
}
I am quite fresh with Swift and iOS dev so I can't figure this one out still:(
On the button tap just use addSubview() to add a UI element like UIActivityIndicatorView.
In the callback from Parse, remove the view and go to the next controller, calling the segue programatically.
SOLUTION FOR ME:
Ok i fiddled around and found the best way which suites me for now. what I do is from View1 the button is pressed and it tell the view controller of the 2-nd view to fire up the data fetching function.
In the 2-nd vie controller once it trigger the async function for retrieving data from Parse it also shows the activity indicator and make it run.
The user sees an empty screen with an activity spinner running. once the async function gets back the data it pushes it into a an empty label and shutsdown+hides the activity indicator.
Works great for my case. no interface hanging. and clear for the user about what's happening where and when.
Thanks to everyone!
p.s. this works for me because that's the end of the lifecycle for the retrieved data. if there were other functions depending on it - this wouldn't work:(
What about setting the object on the segue's destination View controller and then fetch the object, and then reload the view?
This would be:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "1") {
var svc = segue.destinationViewController as ViewControllerQuotes;
svc.yourObjectWhichIsToFetch = yourObjectWhichYouWantToPass //need to create a variable on the ViewControllerQuotes!
}
}
And then in the ViewControllerQuotes class:
class ViewControllerQuotes:ViewController {
var yourObjectWhichIsToFetch:PFObject?
override func viewDidLoad(){
super.viewDidLoad()
yourObjectWhichIsToFetch.fetchInBackgroundWithBlock({
(object, error) -> Void in
if error == nil {
//update the UI
}
})
}
}
With this code, you don't need to show any loading note (what is really ugly).