I have a tableview, which is a list of football matches. In my navigation I have a "plus" sign where I can add a new match to. This is presented as a popover and it works very fine. Although when I use
dismiss(animated: true, completion: nil) and apply changes to my database.
The tableview does not update and I have to change page in the app, to make it update. '
Any suggesting for making my tableview updating after a popover has been showed? I have tried updating the tableview in viewDidAppear
When you dismiss your popover controller at that time check which is presenting view controller if it is your VC with tableview you want to reload then reload your tableview in viewWillDisappear method of your popoverVC
class PopOverViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let tableVC = presentingViewController as? TableViewController {
DispatchQueue.main.async {
tableVC.tableView.reloadData()
}
}
}
If you want to reload data from a core data database
I don't know what data base you are using but I assume it's Core Data
If it's the case:
create un Unwind Segue (control drag the button you're using to dismiss the controller to the exit icon on top right of controller, give it an identifier).
add this code to both your tableview controller and popoverController
here is the code:
class TableViewController: UITableViewController {
var matches = [Match](){ //Core Data object
didSet {
OperationQueue.main.addOperation { //reloading after adding a new match (safe)
self.tableView.reloadData()
}
}
}
//...
//this unwind function executes after dismissing your popover controller
#IBAction func unwindToThemeList(sender: UIStoryboardSegue) {
matches = //... retreive your data
//this code works with var theme {didset} to make sure new matches are added
}
}
and
class popoverController: UIViewController {
//this section of code lets you do operations before dismissing the controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier else { return }
if identifier == TheNameYouGaveToYourIdentifier {
//Do your saves
}
}
So when you press the button to dismiss, it will trigger the unwind segue, going through override prepare first then through unwind for segue.
prepare section in pop viewController lets you do your saves
unwind section in tableviewController lets you retrieve your data
then to reload efficiently use the didst section
Related
I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234
My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("prepare reached")
//super.prepare(for: segue, sender: sender)
//switch(segue.identifier ?? "") {
//case "TabBar":
guard let TabController = segue.destination as? UITabBarController else {
fatalError("Unexpected destination: \(segue.destination)")
}
TabController.selectedIndex = toggle.selectedSegmentIndex
//default:
//fatalError("Unexpected Segue Identifier; \(segue.identifier)")
//}
}
#IBAction func toggleAction(_ sender: UISegmentedControl) {
print("toggle is now at index: ", toggle.selectedSegmentIndex)
//performSegue(withIdentifier: "TabBar", sender: sender)
//container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"
*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.
When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
tabBarVC.selectedIndex = sender.selectedSegmentIndex
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//called before mainvc viewDidLoad
let destinationVC = segue.destination as? UITabBarController
destinationVC?.selectedIndex = 1
}
}
You can't do what you are trying to do.
With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.
What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.
Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.
I have a TabBarController in my Main.storyboard file.
In my Upload.storyboard I am presenting a ViewController from the Main.storyboard file, however, it doesn't contain the tab bar.
The viewProfile button should go to a tab called Sharks and within that present a view controller based on data gathered in the Upload.storyboard (modal view).
Can I add the tab bar programmatically or am I not properly presenting the correct VC?
// MARK: - Actions
#IBAction func viewProfileButtonPressed(_ sender: UIButton) {
let stb = UIStoryboard(name: "Main", bundle: nil)
let sharkProfile = stb.instantiateViewController(withIdentifier: "sharkProfile") as! SharkProfileTableViewController
self.present(sharkProfile, animated: true) {
// add tab bar here?
}
}
What you need to do is present the tab bar view controller, not the view controller that is embedded in it
One method is to create a Delegate Protocol to allow a tap on View Profile button to "call back" to the View Controller that presented it. When that callback is received, the VC sets the "current" tab.
It will look something like this:
// define "call back" delegate protocol
protocol EncounterUploadedDelegate : class {
func didTapSharkProfileButton()
}
The Encounter view controller will need to conform to that protocol:
class EncounterViewController: UIViewController, EncounterUploadedDelegate {
// the normal stuff for this VC and all the other code for it
// ...
// conform to the protocol
func didTapSharkProfileButton() -> Void {
// when we get this call-back, switch to the Shark Profile tab
// tabs are zero-based, so assuming "SharkProfile" is
// is the 4th tab...
self.tabBarController?.selectedIndex = 3
}
// assuming a "Show Modal" segue named "ShowEncounterUploadSegue" is used
// to present the Modal View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEncounterUploadSegue" {
let vc = segue.destination as! TabModalVC
vc.encounterDelegate = self
}
}
}
The view controller to be presented as modal:
class TabModalVC: UIViewController {
weak var encounterDelegate: EncounterUploadedDelegate?
#IBAction func profileButtonTapped(_ sender: Any) {
// dismiss self (the modal view)
self.dismiss(animated: true, completion: nil)
// this will call back to the delegate, if one has been assigned
encounterDelegate?.didTapSharkProfileButton()
}
}
Hope that all makes sense :)
I have a ViewController(VCA) with a TableView inside. From this ViewController it is possibile to call another ViewController (VCB). In this second VC it is possibile add an item to the plist used to populate the TableView in VCA. The problem is that when I save the new item and dismiss the VCB, I can't reload the TableView in VCA.
I have found a lot of examples:
How can you reload a ViewController after dismissing a modally presented view controller in Swift?
How to call reload table view after you call dismissViewController in swift?
How to reload tableview from another view controller in swift
Update the data of a TableViewController after add item
Update the first view after dismissing Popover
Table view is not getting updated after dismissing the popover?
after reading i tried with this code:
**IN VCB**
import UIKit
protocol AddItemDelegateProtocol {
func didAddItem()
}
class VCB: UIViewController {
var delegate : AddItemDelegateProtocol?
...
}
#IBAction func saveButton(_ sender: Any) {
....
self.delegate?.didAddItem()
dismiss(animated: true, completion: nil)
}
**In VCA**
class VCA: UIViewController, UITableViewDelegate, UITableViewDataSource, AddItemDelegateProtocol {
let addItemVC = VCB()
...
override func viewDidLoad() {
super.viewDidLoad()
addItemVC.delegate = self
...
}
func didAddItem() {
self.tableView.reloadData()
}
but this doesn't work. I don't
understand where I'm wrong. Could
you help me?
EDIT: my Solution
I solved in this way:
I've created a singleton in which I declare:
class DataManager {
static let shared = DataManager()
var firstVC = VCA()
.....
}
then, in viewDidLoad of VCA:
DataManager.shared.firstVC = self
now, in the saveButton of VCB, i can call:
#IBAction func saveButton(_ sender: Any) {
........
DataManager.shared.firstVC.tableView.reloadData()
dismiss(animated: true, completion: nil)
}
you can do this in two way :-
1)
Do One thing in VCA
VCA
override func viewWillAppear(_ animated: Bool){
tableView.reloadData()
}
If this does not work out then try this.
2)
create an instance of VCA in VCB and whenever you move from VCA to VCB pass the value of VCA to the instance of VCB and from there reload the table.
VCB
var instanceOfVCA:VCA! // Create an instance of VCA in VCB
func saveButton(){
instanceOfVCA.tableView.reloadData() // reload the table of VCA from the instance
dismiss(animated: true, completion: nil)
}
VCA
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
VCB.instanceOfVCA = self // Pass the value of VCA in instance of VCB while navigating through segue
}
Here you are calling table's reload data when the viewcontroller is not yet shown. i.e, Even before you dismissed the viewcontroler VCB and viewcontroller VCA is shown, you are calling reloadData.
Try calling reload data in VCA's viewWillAppear(animated: Bool) function.
Try this: make changes as below
let addItemVC : VCB? = nil
In ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
addItemVC = (storyboard?.instantiateViewController(withIdentifier: "ViewControllerID") as! SelectionViewController?)! // change ViewControllerID with your controller id
addItemVC.delegate = self
}
The code in your question is perfectly good. I use the same approach and it works like a charm.
IMHO the problem in your code is that you only refresh the table view with self.tableView.reloadData(), BUT may be you forget to refresh your data model - the data source for the table view. E.g. if you delete an entity from Core Data then you need to refetch your data and only after that reload the table view.
I managed to do it using delegate/protocol that is usually used to pass data between view controllers but in this instance I just called the function without passing data and inside this function i put ( tableView.reloadData() ) and it worked like a sweet :)
juts google "Passing data between view controllers and use the method as I explained above"
I want to make button which save data and unwind to another controller (UITableViewController). My button is succeeded save data, but I don't make to unwind to another controller.
My button
#IBAction func buttonDone(sender: UIBarButtonItem) {
saveText() // it is a function of save data
[back]
}
A function
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "first") {
// pass data to next view
var backToFirst = segue.destinationViewController as FirstTableViewController
}
}
I make next
let back = prepareForSegue
To perform the unwind segue to have to call performSegueWithIdentifier("myUnwindSegueID") from the view controller that you're unwinding from.
You also need to make sure you've set up the unwind segue in IB. Have a look at this in case you haven't: http://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Assuming it's within a navigationcontroller simply call
self.navigationController!.popViewControllerAnimated(true)
help me please. how to make a button to do some operation or change to another view? I want that by pressing on it, commands were complete then need that moved on another view. please tell me how else to do to another kind in which a transition is updated when you press the button, all the same. And there were set data from datamodel
#IBAction func addNew(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Items", inManagedObjectContext:managedObjectContext!)
var item = Items(entity: entity!, insertIntoManagedObjectContext:managedObjectContext)
// I make the init from row coredata
item.titleIt = titleItem.description
item.textIt = textViewItem.description
var error: NSError?
managedObjectContext?.save(&error)
// MasterViewController.setValue(Items(), fromKey: "titlIt")
}
Add this at the end of your method:
let viewControllerToGoTo = ClassNameOfViewController()
self.presentViewController(viewController, animated: true, completion: nil)
Note: You may need to instantiate the view controller you want to present in a different way than just (). For example, you may want to load a view controller defined in a nib or storyboard.
To move to another view controller, you write these lines:
var viewController = ViewControllerNew()
self.presentViewController(viewController, animated: true, completion: nil)
But if you want to pass data to another view, I'd recommend using this method instead:
In your storyboard, select the view controller and drag from the blue-outlined yellow square at the top of the view controller to another view controller. A popup will appear, showing a list of segues. Select the item named 'Show'. Segues are placeholders for presenting view controllers -- you can call them at any time in your program. Select the segue, and under the menu on the right hand side type a name, for example 'toOtherScreen' into the text field labeled 'Identifier'. This will let you call the segue from a specific name.
Now go back to your IBAction for the button, and type this:
self.performSegueWithIdentifier("toOtherScreen", sender: self)
This will transition to the other screen. But what if you want to pass data to a screen, or do something when a segue is about to happen? Luckily for you, UIViewController class has a method named 'prepareForSegue', as shown below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toOtherScreen" {
println("yes")
}
}
In this, we check if the segue has an identifier of "toOtherScreen", and if so, print "yes" to the logs. Now, to pass data to another view controller, it is a little more tricky. In the other view controller file, add a variable at the start of the class:
class OtherViewController: UIViewController {
var dataPassed: String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
And in your prepareForSegue method in the main view controller, type this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toOtherScreen" {
let newViewController = segue.destinationViewController as OtherViewController()
newViewController.dataPassed = "NEW DATA"
}
}
Now it will change the variable named dataPassed in OtherViewController to 'NEW DATA'. You can see how you can achieve a lot through this simple way of passing data to other view controllers.
Hope I helped.