I have a view controller class in my app:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var usersTable: UITableView!
var pendingRequest: FBRequest?
var pendingLoginForSlot: Int!
var userID: String?
var swipeGesture: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
usersTable.delegate = self
usersTable.dataSource = self
self.pendingLoginForSlot = -1
self.usersTable.tableFooterView = UIView(frame: CGRectZero)
}
I want this view controller to be initialized and completely functional in my app delegate after didFinishLaunchingWithOptions When I define this view controller in the app delegate like so:
var VC: ViewController = ViewController()
The VC.usersTable is nil. But when I do my function after viewDidLoad everything is working fine.
So how can I fire functions from within app delegate (e.g. logging in users that are defined in the tableview) and thus have the properties available that get defined in viewDidLoad and expect the same results after I load the VC in a regular manner by loading it via a tab barcontroller?
Thanks!
Related
This question already has answers here:
Instantiate and Present a viewController in Swift
(18 answers)
Closed 2 years ago.
I am building a running app and am trying to programmatically present a view controller when the start run button is tapped. However, when I tap the button after setting up the presenting view controller, any reference to an IBOutlet is found nil when unwrapping in the order that they are called.
It's worth noting that when I connect the two view controllers with "show" via storyboard instead, that everything works fine, but I want to present the view controller programmatically and in fullscreen rather than the cardlike presentation by default.
From the presenting view controller:
#IBAction func startRunTapped(_ sender: Any) {
let inRunVC = CurrentRunVC()
inRunVC.modalPresentationStyle = .fullScreen
self.present(inRunVC, animated: true, completion: nil)
}
From the presented view controller:
import UIKit
import MapKit
class CurrentRunVC: LocationVC {
#IBOutlet weak var sliderImageView: UIImageView!
#IBOutlet weak var swipeBGImageView: UIImageView!
#IBOutlet weak var durationLabel: UILabel!
#IBOutlet weak var paceLabel: UILabel!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var pauseButton: UIButton!
var startLocation: CLLocation!
var lastLocation: CLLocation!
var timer = Timer()
var runDistance = 0.0
var pace = 0
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
let swipeGesture = UIPanGestureRecognizer(target: self, action: #selector(endRunSwiped(sender:)))
sliderImageView.addGestureRecognizer(swipeGesture) //error unwrapping here
sliderImageView.isUserInteractionEnabled = true // and here
swipeGesture.delegate = self as? UIGestureRecognizerDelegate
}
//errors also on any other reference to an IBOutlet
I've confirmed that all IBOutlets are connected properly.
Storyboard:
Thanks in advance
You are not grabbing the correct storyboard instance of CurrentVC but you are creating a new one. Instead of let inRunVC = CurrentRunVC(), use
let runVC = self.storyboard?.instantiateViewController(withIdentifier: "CurrentRunVC") as! CurrentRunVC //set the identifier in the storyboard identity inspector
Your view controller's outlets are defined in the storyboard, and created when the view controller is loaded from the storyboard. When you just initialise a new view controller in this line:
let inRunVC = CurrentRunVC()
Then the system will look for a xib file with the same name as the view controller, which doesn't exist, which means it just loads a blank view with no connected outlets. All those implicitly unwrapped optionals are now nil.
Either make a new segue from the button to the same view controller, but a modal presentation, or add a reference to the presented view controller in the storyboard and then load it using the instatiate with identifier method of the current storyboard.
I have an app written in Swift for iOS 13 where I present a view modally using storyboards. Once the new view is being presented, I want the parent to call a method which is located inside the child view controller (of my custom class which inherits from UIViewController).
To do this, I plan to have a method inside my parent view controller that gets the modal view controller being presented as its child. Once I get this reference, I will call the child's function from my parent view controller.
I realise this is probably a bad design decision, but I haven't found a way to avoid this approach. I have looked all over stackoverflow to find an answer, but I haven't found any yet. Any help would be much appreciated.
You can instantiate the child view controller and set its properties before presenting it. Then the code that changes the child view controller based on the data is put in the viewDidLoad() method.
class ParentViewController: UIViewController {
func goToChildViewController(object: CustomObject) {
guard let childViewController = self.storyboard?.instantiateViewController(withIdentifier: "child") as? ChildViewController else { fatalError("Cannot instantiate child view controller!") }
childViewController.myProperty = true
childViewController.myObject = myObject // Example of how to pass data from a data model
self.present(childViewController, animated: true)
}
}
class ChildViewController: UIViewController {
var myProperty = false
var myObject: CustomObject? = nil
override viewDidLoad() {
if myProperty {
// Conditional code here
}
{
}
Alternatively, you could trigger a segue in code instead of presenting the child view controller directly.
In this case, you would set up the child view controller inside the parent view controller’s overridden prepare(for:sender:) method, where the child view controller can be accessed using segue.destinationViewController.
Through Segue
When segue triggered maybe through a button press or a table view selection prepare(for:) method will be called on your view controller, at this point you can configure your DestinationViewController by setting some properties.
RootViewController.Swift
#IBOutlet weak var textFieldFirstName: UITextField!
#IBOutlet weak var labelFullname: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let firstVC = segue.destination as? FirstViewController else { return }
firstVC.firstname = textFieldFirstName.text
}
After typing the firstname and tap enter button , firstname value is passed to firstViewController and assigned to related UILabel in viewDidLoad() Method.
FirstViewController.Swift
#IBOutlet weak var textFieldLastName: UITextField!
#IBOutlet weak var labelFirstName: UILabel!
var firstname: String?
override func viewDidLoad() {
super.viewDidLoad()
labelFirstName.text = “Firstname: \(firstname ?? “”)”
}
You can achieve same thing through closure and Delegates
I have an app with multiple view controllers. I am implementing a search bar to navigate a table view that is in each of these view controllers.
I have chosen to implement the search controller using a custom class, where I handle all the search logic. In order to make this possible, I am currently using a superclass from which each view controller inherits. I would like to know if there is a way for me to make this work without subclassing.
Here is the current implementation of my SearchController class:
class SearchController: NSObject, UISearchBarDelegate {
/* This is the trouble spot. If I change this to UIViewController?,
I get the compiler error "value of type UIViewController has no member tableView" */
weak var viewController: BaseViewController?
/*
... rest of SearchController implementation
includes methods that interact with view controller table views
*/
}
this is the BaseViewController class:
class BaseViewController: UIViewController {
let searchController = SearchController()
let tableView = UITableView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
/*
... rest of BaseViewController implementation
*/
}
To summarize, the issue I am having is that I have several view controllers with tableviews and I can't seem to make this work without creating a new base class that they can inherit from. Using UIViewController simply won't work because the UIViewController class does not have a tableView property built into it.
Any ideas?
You do not need to force all your viewControllers to subclasses BaseViewController. If the only requirement is for the viewController to have a tableView property then define a protocol with that requirement and make the relevant viewControllers implement that protocol.
Rewriting your example:
protocol BaseControllerProtocol: class {
var tableview: UITableView { get }
}
class SearchController: NSObject, UISearchBarDelegate {
//We store any class that implements the BaseControllerProtocol protocol
//Now you can use viewController.tableview
weak var viewController: BaseControllerProtocol?
//If you what to have UIViewcontrollers instances only use:
//weak var viewController: (UIViewController & BaseControllerProtocol)?
}
//An example of a viewcontroller that implements the BaseControllerProtocol
class ARandomViewController : UIViewController, BaseControllerProtocol {
var tableview: UITableView = UITableView()
}
I think you could still use UIViewController? if you assign its UITableView to your SearchViewController's private tableView calculated instance variable like so:
class SearchController: NSObject, UISearchBarDelegate {
weak var viewController: UIViewController?
fileprivate var _tableView: UITableView? {
if let vc = self.viewController {
for subview in vc.view.subviews {
if let tableView = subview as? UITableView {
return tableView
}
}
}
return nil
}
// Whatever methods interact with table view should now use self._tableView.
func doSomething() {
guard let tableView = self._tableView else { return }
// Do something with the tableView
}
}
I have the following setup:
StartViewController has a ContainerView that contains ContainerViewController
I try to find a way to hidden an element in StartViewController after a task is performed in ContainerViewController.
For this I try to use delegation method like this:
StartViewController
class StartViewController: UIViewController, showBannerAdDelegate {
#IBOutlet weak var bannerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
bannerView.hidden = false
}
func bannerAdHidden(status: Bool) {
bannerView.hidden = status
}
}
ContainerViewController
protocol showBannerAdDelegate: class {
func bannerAdHidden(status: Bool)
}
class ContainerViewController: UIViewController {
weak var delegate: showBannerAdDelegate! = nil
#IBAction func buttonPressed(sender: UIButton) {
delegate.bannerAdHidden(true)
}
}
If I presented the ContainerViewController I could do in prepareForSegue
let destination = segue.destinationViewController as! ContainerViewController
destination.delegate = self
But in this case both View Controller are always present.
What code should I add to the View Controller to make it work?
Thank you,
If one of the view controllers is inside a container view then it is loaded with an embed segue, which fires when the containing view controller is first loaded. The prepareForSegue method still gets called, so you can set up a delegate exactly as you've described. I always thought embed segues were a little odd (it's not really a segue, more like loading a child view controller) but that's how it works.
I have a tab bar class (that is attached to my tab bar controller), Like so:
class CaptionTabBarController: UITabBarController, UITabBarControllerDelegate {
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
var logView = self.viewControllers![2] as CaptionsController
logView.log.append("test working!")
}
override func awakeFromNib() {
self.delegate = self;
}
}
And my receiving viewcontroller is like this:
class CaptionsController: UIViewController {
#IBOutlet weak var captionSearchBar: UISearchBar!
#IBOutlet weak var captionsTitle: UILabel!
var receiveImage:UIImage!
var receiveCategoryText:String!
var log = [String]()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println(log)
}
}
This works when I'm explicitly setting logView.log in CaptionTabBarController.
The result I get in my output windows is as expected. Each tabbar item I click adds "test working!" to the array.
My question is:
How would I be able to get a value from another viewcontroller class to CaptionsController using the tabBarController method I am employing?
This view is a part of a "child" of the tabbar itself, so I'm assuming it already has an instance. All examples I've found just show this, but not how to get data from another class.
The UIViewController that wants to pass the data can store it on your AppDelegate class. Then the UITabBarController delegate method can pull it off and set properties on the receiving UIViewController.
Also, assuming your app is based on the Tab Controller, your AppDelegate can find it with window?.rootViewController as UITabBarController.