Below is a screenshot of the storyboard for my project:
As you can see, I have a UIViewController with a UITableView embedded in a UINavigationController. When the user adds an item to the UITableView a new Realm Object model which I have named Project is created (I'm using RealmSwift) and they are taken to the first item in the UITabBarController. I'm wondering if it is possible to associate the UITabBarController and all of its child view controllers with the Project that is created.
Here is the code that creates the Object (which is working fine):
func add(withName projectName: String) {
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue) {
autoreleasepool {
let realm = try! Realm()
let newProject = Project(value: ["name": projectName, "date": NSDate()])
realm.write({ () -> Void in
realm.add(newProject)
})
}
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
selectedProject = array[indexPath.row]
print(selectedProject)
self.performSegueWithIdentifier("showTab", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTab" {
let dvc = segue.destinationViewController as! TabBarViewController
dvc.project = selectedProject
}
}
As you can see, I'm passing the new Project to the UITabBarController.
Here is my code in the UITabBarController:
import UIKit
import RealmSwift
class MasterTabViewController: UITabBarController {
var project: Project?
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - UI Styling
private extension MasterTabViewController {
func configureNavigationBar() {
navigationItem.title = project?.name
}
}
This sets the title of all UIViewControllers that descend from the UITabBarController.
Is there a way for me to make that Project object available to all view controllers that descend from the UITabBarController? I haven't been able to find too much information on this. One suggestion that I found was to store the Object in NSUserDefaults but it seems to me that there must be a better way.
EDIT: I forgot to add that I can think of a way to do this with Core Data using NSManagedObjectContext, but I'm having some trouble seeing the equivalent in Realm.
You were on the right track! When a view controller is a child of a UITabBarController, you can access the tab bar controller from inside the child view controller with the self.tabBarController property.
Since you know these view controllers are children of a subclass of UITabBarController that also provides Project as a property, it should be a matter of simply calling that tab bar controller property, typecasting the resulting view controller to your tab bar controller subclass, and then calling the project property on it.
So, in any of your child controllers:
let project = (self.tabBarController as MasterTabViewController).project?
You could easily optimize this further by making project a read-only property of your child controller classes that calls this line of code automatically. Good luck!
Related
I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.
I have gone through all the other posts about this topic but they don't seem to help me.
I have a UITabBarController that is launching two tabs. I want to pass data collected in tab1 to the UITabBar ViewController. I am trying to use delegete protocol for this but I am having trouble setting the delegate variable in the sending ViewController. The prepare for segue never gets called. I cannot even cycle through the viewcontrollers of the tabs inside the ViewDidLoad() of the Tabbar controller as they are not created yet and so nil.
I have used delegates before and it seems rather straightforward. Does it matter that I am using it in a Tabbar?
When I run the code the viewDidLoad() in TabBarViewController is called but not the preparefor segue.
The IBAction donePressed in the MeViewController is called but the delegate is not called as its not set.
Here is the code --
protocol DetailsDelegate: class {
func myDetailsGathered( myDetails: MyDetails )
}
/// RECEIVING VIEW CONTROLLER
class TabBarViewController: UITabBarController, DetailsDelegate
{
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
print("prepare for segue called\n");
if let destinationViewController = segue.destination as? MeViewController
{
destinationViewController.delegate = self
}
}
override func viewDidLoad()
{
print("ViewDidLoad Called \n")
}
func myDetailsGathered(myDetails: MyDetails)
{
self.myDetails = myDetails
print("My details gathered \n")
}
}
---------------
/// SENDING VIEW CONTROLLER
class MeViewController: UIViewController
{
weak var delegate: DetailsDelegate?
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
// I have UIButton in the view and this is invoked when its pressed.
#IBAction func donePressed(_ sender: Any)
{
var infoToPass = MyDetails()
print("looks like we are done")
delegate?.myDetailsGathered(infoToPass: myDetails)
}
}
prepareForSegue is called when you perform a segue. Which you don´t do and that´s why it does not get called.
A segue defines a transition between two view controllers in your
app’s storyboard file.
You should use a singleton class to store variables and access them between different controllers. You declare one like this:
class Singleton {
static let sharedInstance = Singleton()
var name = ""
}
Assign to Singleton:
Singleton.sharedInstance.name = "Some name"
To read from it from whatever controller:
let name = Singleton.sharedInstance.name
First of all, why do you want your tabbarController to receive some info/data though?
The prepare for segue never gets called.
prepareForSegue method will be invoked right after the performSegue. So where's your performSegue method? Or are you sure that that kind of segue going to MeViewController is being performed?
One more option you have is to use NotificationCenter.
I have the following class below. The idea is it will use a custom Progress Window View Controller to handle progress of various different events. The problem is since this is in a class and not a view controller it's self, I'm not sure how to make the progressWindow actually show up after I instantiate it from the storyboard?
How do I do this? Currently I get an error that the application tried to present model view controller on itself.
import Foundation
import UIKit
class StatusProgress{
static var cancelCode = {}
static var runCode = {}
static var theProgressWindowController = ProgressWindowViewController()
static var returningViewControllerIdentifier = ""
static let storyboard = UIStoryboard(name: "Main", bundle: nil)
static func run(){
// This will run in parralel but on main queue. Has to be on this Queue because it might involve UI
dispatch_async(dispatch_get_main_queue(), {
// Update the UI on the main thread.
StatusProgress.runCode()
});
}
static func cancel(){
dispatch_async(dispatch_get_main_queue(), {
StatusProgress.cancelCode()
dispatch_sync(dispatch_get_main_queue(),{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(returningViewControllerIdentifier)
vc.presentViewController(vc, animated: true, completion: nil)
})
});
}
static func show(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil) //use own instance to show it's self? (throws error! application tried to present modal view controller on itself. Presenting controller is <Inventory_Counter.ProgressWindowViewController: 0x1466ea390>.')
})
}
}
My problem is essentially I need a replacement for this line of code.
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil)
I forgot to mention here is the code that runs it inside another view controller.
SyncViewController.swift
import UIKit
class SyncViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func yesSyncButtonAction(sender: UIButton) {
StatusProgress.returningViewControllerIdentifier = "syncWindow"
StatusProgress.runCode = {
print("run code test")
}
StatusProgress.cancelCode = {
print("cancel code test")
}
StatusProgress.show()
}
#IBAction func noSyncActionButton(sender: UIButton) {
tabBarController?.selectedIndex = 1 //assume back to inventory section
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The biggest problem is that your StatusProgress class is instantiating and showing a view controller. View controllers should instantiate and show other view controllers, model objects should not. So you need to move the logic for presenting the new view controller into you SyncViewController. Then use delegation to communicate to the SyncViewController that the syncing is done.
protocol StatusProgressDelegate {
func statusProgress(status: StatusProgress, shouldShow: Bool)
func statusProgress(status: StatusProgress, shouldCancel: Bool)
}
Your StatusProgress object would have a delegate that conforms to that protocol and call that delegate inside of its show and cancel methods. This means that you need to make the static functions instance methods, and write an initializer for the class so you can instantiate it.
If the view life cycle events are not much important for you, you may just add the view of your progress controller to view of your current controller. or it's even better if you supply the UIView parameter in your show() function.
static func show(attachToView: UIView ){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
attachToView.addSubview(theProgressWindowController.view)
})
}
After all you'd better to remove your progress view from superview
static func cancel(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.view.removeFromSuperview()
})
}
In the struggle of developing a Tabbed IOS App with Swift 1.2 and Xcode 6.3 based on MVC, I'm using the visual Storyboard elements instead of to do it programatically because I'm not an experienced developer. In the image attached below you can see the Architecture in the StoryBoard of the App:
The App consists in:
One TabBarController with four TabBar Items.
Each Item has its own ViewController in Storyboard.
All of them are linked with the relationship seque(ViewControllers) in StoryBoard.
Each ViewController in the StoryBoard has its own Class.
The last Item has an embedded NavigationController because I'm using a PageMenu project https://github.com/uacaps/PageMenu to include a paging menu controller with a two child ViewControllers
The Issues I'm having until this point are:
The two child ViewControllers are not linked with the Last TabBar Item in the StoryBoard,as you can see in the figure above, only are instantiated in the parent ViewController Class(PageMenuViewController1), normally this PageMenu works but sometimes the last TabBar Item dissapears, I'm very confused with this issue.
The override func viewWillAppear into the default child ViewController is called twice at the first time, I've include a println("ClubsController viewWillAppear").
The code of the ViewControllers is
import UIKit
class ClubsViewController: UIViewController, UITableViewDataSource{
#IBOutlet var tableview:UITableView!
let apiClient = ApiClient()
var clubs: [Club]!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("ClubsController viewWillAppear")
apiClient.clubsService.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableview?.reloadData()
}
else {
println("error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.clubs?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("clubObjectCell") as! ClubTableViewCell
cell.clubObject = self.clubs?[indexPath.row]
return cell
}
}
The code of the PageMenuViewController is
import UIKit
class PageMenuViewController1: UIViewController {
var pageMenu : CAPSPageMenu?
override func viewDidAppear(animated: Bool) {
println("PageMenuViewController1 viewWillAppear")
super.viewDidAppear(animated)
// Array to keep track of controllers in page menu
var controllerArray : [UIViewController] = []
// Create variables for all view controllers you want to put in the
// page menu, initialize them, and add each to the controller array.
// (Can be any UIViewController subclass)
// Make sure the title property of all view controllers is set
// Example:
var controller1 = storyboard!.instantiateViewControllerWithIdentifier("ClubsViewController")! as! ClubsViewController
controller1.title = "CLUBS"
controllerArray.append(controller1)
var controller2 = storyboard!.instantiateViewControllerWithIdentifier("PartiesViewController")! as! PartiesViewController
controller2.title = "PARTIES"
controllerArray.append(controller2)
// Customize page menu to your liking (optional) or use default settings by sending nil for 'options' in the init
// Example:
var parameters: [CAPSPageMenuOption] = [
.MenuItemSeparatorWidth(4.3),
.UseMenuLikeSegmentedControl(true),
.MenuItemSeparatorPercentageHeight(0.1)
]
// Initialize page menu with controller array, frame, and optional parameters
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
// Lastly add page menu as subview of base view controller view
// or use pageMenu controller in you view hierachy as desired
self.view.addSubview(pageMenu!.view)
}
}
Appreciate help to accomplish the best practices until this point.
I've not familiar with the CAPSPageMenu but there is nothing wrong with having scenes in a storyboard that aren't connected with a segue - this is just a convenience to help with transitions, and instantiating them with instantiateViewControllerWithIdentifier is totally legitimate.
Something that does stand out looking at your storyboard is the way your table view controller with the navigation view controller is wired up.
The navigation viewcontroller should have the relationship with the tab bar controller - not the table viewcontroller.
Here's a screenshot of how the connection should look. Possibly this is why you're sometimes loosing a tab.
I am currently trying to pass data between two UINavigationControllers with a UITableViewController attached to each. I am navigating between these two controllers via a UITabBarController. I have been trying to use vacawama's solution on Changing VC issue in Swift. How to pass data between views in tab bar controller? I am using the following code.
import UIKit
class placeData: Equatable {
var description : String
var selected : Bool
init (description : String, selected : Bool) {
self.description = description
self.selected = selected
}
}
class PlacesTabBarController: UITabBarController {
var placeDataArray = [placeData]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This is in the tabBarController custom class. In the other two controllers I declare the array I want to be pass between the view controllers and use the method in the link to populate the UITableViewController
var placeDataArray = [placeData]()
override func viewWillAppear(animated: Bool) {
placeDataArray = (self.tabBarController as PlacesTabBarController).placeDataArray
}
When the controllers load, however, the arrays are empty. In the example in the link, all the rest of the code is within the viewWillAppear function, where I need my array to be available to all of the corresponding tableView functions. I am not sure if I am just equating the arrays to zero on load. But my thought was that they would repopulate. Not sure what the correct way to go about this is. Any help would be great.
EDIT: My current structure is as follows:
UITabBarController
| |
UINavigation UINavigation
Controller Controller
| |
UITableView UITableView
Controller Controller
The array is populated when the first tab loads. What I am trying to do is have the populated array in the second view controller, and if I edit it in the second view controller, I want the edits to stay in the first. So I want it to be passed by reference.
I would subclass the UITabBarController and make it a delegate for the two UITableViewControllers.
CustomTabBarController
protocol CustomTabBarDelegate {
var places:Array<placeData> { get set }
}
class CustomTabBarController: UITabBarController, CustomTabBarDelegate {
var places = Array<placeData>()
override func viewDidLoad() {
places = [PlaceData(),PlaceData()]
var table1 = CustomTableViewController()
var table2 = CustomTableViewController()
table1.delegate = self
table2.delegate = self
var navController1 = UINavigationController(rootViewController: table1)
var navController2 = UINavigationController(rootViewController: table2)
self.viewControllers = [navController1, navController2]
}
}
Then your TableViewControllers simply access the delegates array like so.
CustomTableViewController
class CustomTableViewController: UITableViewController {
var delegate:CustomTabBarDelegate!
override func viewDidAppear() {
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return delegate.places.count
}
}
Any changes to the array will be visible in each table after you .reloadData() - I have set the CustomTableViewController in my example to reload data every time the view appears, so when you change tabs they should refresh to show the latest changes.
It's worth mentioning that in time it would probably be cleaner to have a separate class that manages your data instead of holding the array in the TabBarController.
You can always use the app delegate..
Set a property there and call it from anywhere in your application.
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var yourNeededData = appDel.yourPassingAroundDataProperty