Where to place code to populate a UITextfield (title field) after a segue screen returns to main screen? - ios

I am building a Swift - Single View app. I’ve added a “segue" view to allow user to store a title line preference using NSUserDefaults. When I return to the primary view the title line isn’t filled with the new title info from NSUserDefaults. I’ve placed the code to populate the main screen title line in the viewDidLoad section noted below. Strangely enough, if I go to the “segue" screen and back to main screen again without making changes then it shows up. Where can I place code to get the title line stored in my preferences segue screen to show up when I go back to the main view? Here is my code that works, but only after going back and forth from the seague twice. Thanks for any help.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let defaults = NSUserDefaults.standardUserDefaults()
let titlePref = defaults.objectForKey("usertitleLineKey")!
titleLine.text = titlePref as! String
}

You should use viewWillAppear or viewDidAppear callback methods to refresh any content in your view when or just before it shows up. viewDidLoad will only be called once when the view is initialised on to memory.

Try this code:
override func viewWillAppear() {
let defaults = NSUserDefaults.standardUserDefaults()
let titlePref = defaults.objectForKey("usertitleLineKey")!
titleLine.text = titlePref as! String
}

Related

SplitView - reload data in master tableView based on detail changed in swift

I create splitView with tableView in master View and tableView with static cells in two details view (see on picture).
App Structure
Names of view controllers are:
DocsTableViewController - master view (on the left)
DocDetailTableViewController - detail view (on the top right)
DocEditTableViewControler - second detail view (on the bottom right)
DocsTVC is list of doctors, DocDetailTVC is detail of doctor user selected in DocsTVC and on DocEditTVC user can edit data of doctor showed in DocDetailTVC or add completely new one (based user clicked on edit or add button).
All these things are working fine - show detail, show edit form and save edited or new doc. Problem is the master table view. I'm trying to reload it in detail after saving edited/new item and by print I see that data are reload but not the table. Table is showing still the same and I have to go to same other screen of app and then go back to see reloaded table view. What should I do?
After saving doctor on DocEditTVC I'm going back to detail to this method:
#IBAction func saveToDocViewController (segue: UIStoryboardSegue){
let controller = DocsTableViewController()
controller.tableView.reloadData()
print("[SPLIT VIEW] detail: \(controller.docs.count)")
}
And some codes from DocsTableViewControler (the master view):
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
// menu
if inputsMenuButton != nil {
inputsMenuButton.target=self.revealViewController()
inputsMenuButton.action=#selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
// navigation bar
self.navigationController?.navigationBar.barTintColor = OAKScolor().colorOAKSmain()
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
// data load
print("[SPLIT VIEW]: ViewDidLoad")
dataLoad()
self.tableView.reloadData()
// split view
self.splitViewController?.delegate = self
self.splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
}
and
func dataLoad() {
print("[SPLIT VIEW]: DataLoad")
let arrayOfDocIDs = PlistManager.sharedInstance.getArrayOfValuesFromDict("DocID") as [AnyObject] as! [String]
for id in arrayOfDocIDs {
docs.append(DocData(docID: id).docLoad(false))
}
print("[SPLIT VIEW] table: \(docs.count)")
}
By these prints I see that evening is going well - view did load on the master view and array of docs write new count. But the tableView is not changed. Thank you for any help.
With this code
let controller = DocsTableViewController()
controller.tableView.reloadData()
You created new Master controller instance, not the current one.
There is 2 ways to solve it:
In Detail Controller, create a reference to current master controller.
Use Delegate. Here is a good explanation:
https://www.natashatherobot.com/explaining-ios-delegates-a-story-about-your-morning-coffee/#
Hope this help!

Xcode with swift: Is it possible to show a view controller upon another without resetting the first?

I'm creating a simple game with a pause button. I want to show another view controller with the pause screen without resetting the game view controller when i return. Every thing i have tried, has resulted in the game view controller being reset, when i press the resume button and return to the game.
Does anybody know how i can do this without resetting the game?
It's a sprite kit and swift iOS app in xcode.
You can show a pop-up based view. You can create .xib and design your pause view as you wish. Then you can resume the game without resetting anything.
Sample code:
// Set Pause Page
var pauseView = PauseView.loadFromNib() // need an extension
#IBAction func Pause(sender: UIButton) {
pauseView.btnResume.addTarget(self, action: #selector(resume(_:)), forControlEvents: .TouchUpInside)
// set place on view
view.addSubview(pauseView)
}
func resume(sender:UIButton) {
pauseView.removeFromSuperview()
}
I created a demo for you:
Full demo available on Github: Get Source Code
Create a variable in your original view controller:
var shouldReset = true
Then, when you perform a segue back, add this in your prepareForSegue:
let vc = destination as! //Your view controller
vc.shouldReset = false
In your viewDidLoad (or wherever you reset your data):
if shouldReset == true {
//Reset
}

Short lag when setting label text using storyboards

In my application, I have a mainViewController with some content on it. At some points, I load an overlay view controller from storyboard. The overlay view controller is smaller than the screen and is presented on top of the mainViewController. I initialize it the following way:
class MyOverlayViewController {
#IBOutlet var textLabel: UILabel!
#IBOutlet var countLabel: UILabel!
static let storyboard = UIStoryboard(name: "...", bundle: nil)
// Return a new view controller
class func newViewControllerWithData(data: AnyObject) -> UIViewController {
let vc = storyboard.instantiateViewControllerWithIdentifier("MyOverlayViewController") as! MyOverlayViewController
Timing.performAfterDelay(0) {
vc.titleLabel.text = data[...] // Load title label text
vc.countLabel.text = data[...] // Load count label text
}
return vc
}
}
I cannot set the text of the labels immediately in the method newViewControllerWithData, because that produces the following error: fatal error: unexpectedly found nil while unwrapping an Optional value. So the labels are nil when accessing them immediately in this method.
It seems like the two label outlets are not loaded immediately when the view controller is instantiated from storyboard, because this takes a (very) short time.
Therefore, I use my method Timing.performAfterDelay(0) which executes the code after the next run-loop cycle (it starts a timer with duration 0 and executes the code as callback). The code is (I have checked that) executed on the main thread.
The problem is the following:
Sometimes (not always, and not reproducible!), when loading the overlay view controller, for a fraction of a second the labels are empty (like I have defined them in storyboard) before they are showing the text.
So the user sees empty labels for a short moment before the actual data is loaded into the labels.
How can I fix this behavior?
Is it possible somehow to access the outlets immediately after instantiating the view controller from storyboard, without using Timing.performAfterDelay(0)?
Help would be appreciated.
Outlets are set after view is loaded i.e. when viewDidLoad gets called on the view controller. However, calling it directly like vc.viewDidLoad() will not work, you have to access the view controller's view like let dummyVariable = vc.view instead. Here's the code that force loads the view and then sets the label values.
class func newViewControllerWithData(data: AnyObject) -> UIViewController {
let vc = storyboard.instantiateViewControllerWithIdentifier("MyOverlayViewController") as! MyOverlayViewController
let _ = vc.view // force load the view
// now set your outlets as you please
vc.titleLabel.text = data[...] // Load title label text
vc.countLabel.text = data[...] // Load count label text
return vc
}
NOTE: This is not really a good practice though. MyOverlayViewController should be responsible for setting its label values instead of these being set from the outside. You could pass it the required data via a property or argument to a method, etc.

Pass data between three viewController, all in navigationController, popToRootView

The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.

UITableView and UIRefreshControl being moved down for unknown reason

I have a UITableViewController in my app with a UIRefreshControl added to it. Sometimes however (I'm not sure how to reproduce this, it happens every now and then), I get some extra whitespace at the top of the table view with the refresh control being offset even below that.
This is what it looks like (idle on the left, being pulled down on the right):
I don't have any clue what could be causing this. In my viewdidload I'm only instantiating the refresh control and calling an update function that sets the attributed title. I've moved adding the refresh control to the table view into the viewDidAppear as I've read elsewhere. This is what that code looks like:
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
updateData()
}
override func viewDidAppear(animated: Bool) {
refreshControl!.addTarget(self, action: "updateData", forControlEvents: UIControlEvents.ValueChanged)
tableView.insertSubview(self.refreshControl!, atIndex: 0)
}
func updateData() {
//...
ServerController.sendParkinglotDataRequest() {
(sections, plotList, updateError) in
//...
// Reload the tableView on the main thread, otherwise it will only update once the user interacts with it
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
// Update the displayed "Last update: " time in the UIRefreshControl
let formatter = NSDateFormatter()
formatter.dateFormat = "dd.MM. HH:mm"
let updateString = NSLocalizedString("LAST_UPDATE", comment: "Last update:")
let title = "\(updateString) \(formatter.stringFromDate(NSDate()))"
let attributedTitle = NSAttributedString(string: title, attributes: nil)
self.refreshControl!.attributedTitle = attributedTitle
})
}
}
Do you need to add the refresh control as a subview of the tableView? I think all you need to do is assign self.refreshControl. According to the documentation:
The default value of this property is nil.
Assigning a refresh control to this property adds the control to the
view controller’s associated interface. You do not need to set the
frame of the refresh control before associating it with the view
controller. The view controller updates the control’s height and width
and sets its position appropriately.
Adding a subview in viewDidAppear could get executed more than once. If you push a controller from a cell and pop back this will get called again. It could be that insertSubview checks if the refresh already has a parent and removes it first, so might not be your issue. You should only do the insert when the controller appears for the first time.
updateData could also be getting added multiple times.
So I think you only need to assign self.refreshControl and then add a handler for the refresh action as you do now using addTarget but this time do it on self.refreshControl.
You can also do all this from storyboard. In storyboard you select the UITableViewController and on the attribute inspector simply set the Refreshing attribute to enabled. This adds a UIRefreshControl into the table and you can see it on the view hierarchy. You can then simply CTRL drag as normal from the refresh control into the .h file and add an action for valueChange which will be fired when you pull down on the refresh control in the table.
Well, I believe that your described behavior might not necessarily be caused by the refresh control.
According to the fact that you don't have any other subviews below your table view I would recommend you to try to place a "fake"-view below your table view. I usually prefer an empty label with 0 side length.
I had similar issues like yours where my table view insets were broken in some cases. And as soon as I used this "fake" subview the problems disappeared. I've read about this issue in some other threads, too. And the solution was this. Seems to be an odd behavior/bug.
Give it a try :)

Resources