Swift - Run method from another class - ios

I was wondering how I could run a method from another class. For example, something like the following code:
let newVC: ScoreViewController = ScoreViewController()
newVC.makeScore()
The above code won't work for me because in makeScore(), I am changing a label's text, which I can't do with the above code because it is creating a new instance. Is there a way to call a method to be run without creating a new instance so that I can change a label's text in makeScore()?
EDIT:
How ScoreViewController is added to PageViewController:
let vc = storyboard.instantiateViewControllerWithIdentifier("Score") as! ScoreViewController
self.addChildViewController(vc)
self.scrollView.addSubview(vc.view)

I am assuming that you've some method in your FirstViewController where you're changing the score and showing it in your ScoreViewController. The delegation pattern is the possible solution for this problem. In your FirstViewController create a protocol for updating score such as:
protocol FirstVCScoreDelegate:NSObjectProtocol {
func makeScore()
}
Then inside your FirstViewController create a var for this delegate:
var delegate: FirstVCScoreDelegate
Then in your PageViewController, where you are creating the instances of the FirstViewController and ScoreViewController, set the delegate of the FirstViewController to ScoreViewController:
var firstVC: FirstViewController()
var scoreVC: ScoreViewController()
firstVC.delegate = scoreVC
And after this, in your method in the FirstViewController where the score is changing:
#IBAction func scoreChangeAction(sender: AnyObject) {
if delegate.respondsToSelector(Selector("makeScore")) {
delegate.makeScore()
}
}
This will signal the ScoreViewController to update the score. You now have to implement the delegate method inside ScoreViewController:
extension ScoreViewController: ScoreDelegate {
func makeScore() {
//update your label
}
}
I believe this will solve your problem.
UPDATE
Try this in your PageViewController's viewDidLoad: method:
override func viewDidLoad() {
super.viewDidLoad()
let mainStoryboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
let firstVC : FirstViewController = mainStoryboard.instantiateViewControllerWithIdentifier("firstVC") as FirstViewController
let scoreVC : ScoreViewController = mainStoryboard.instantiateViewControllerWithIdentifier("scoreVC") as ScoreViewController
firstVC.delegate = scoreVC
self.addChildViewController(firstVC)
self.addChildViewController(scoreVC)
self.scrollView.addSubview(firstVC.view)
self.scrollView.addSubview(firstVC.view)
}

In the PageViewController, declare a property:
class PageViewController {
var scoreViewController:ScoreViewController
// ....
}
After initializing the ScoreViewController:
let vc = storyboard.instantiateViewControllerWithIdentifier("Score") as! ScoreViewController
Hold it as a property:
self.scoreViewController = vc
Then use
self.scoreViewController.makeScore()

Related

Pushing Data to Another ViewController Without Moving ViewControllers

I am relatively new to coding in Swift. I am trying to pass data back to another view controller without having to segue to that controller so that the user can stay on the same screen. I am looking into using a delegate and wondering if this will be the best way to access the variable on multiple view controllers. Thank you
View controller 1:
func passVariables() {
let passSleepValue = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "sbHome") as! sbHomeController
passSleepValue.getSleepValue = sleepValue
//navigationController?.pushViewController(passSleepValue, animated: true)
}
View Controller 2:
func updateDay() {
let dayScoreAvg = getSleepValue
dayScore.text = String(format: "%.1f", dayScoreAvg)
print("Get sleep Value is \(getSleepValue)")
}
It looks like you can already pass data to second ViewController but you can't pass some data back.
So one way is using delegate pattern.
Start with declaring delegate protocol with method for passing data (in your case it can be just passing String)
protocol SecondVCDelegate: class {
func passText(_ text: String)
}
then create delegate variable in second ViewController
class SecondViewController: UIViewController {
weak var delegate: SecondVCDelegate?
...
}
Now you can implement this protocol to first ViewController
extension ViewController: SecondVCDelegate {
func passText(_text: String) {
// do something with text passed from second VC
}
}
now set delegate of Second VC as self before you push it
func passVariables() {
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "sbHome") as! sbHomeController
viewController.getSleepValue = sleepValue
viewController.delegate = self
navigationController?.pushViewController(viewController, animated: true)
}
then in second VC you can just call method on your delegate and in first VC this method gets called
delegate?.passText("Hello First!")

Swift:Updating UILabel from AppDelegate label doesn't update

I'm trying to update UILabel from appDelegate but the label never change the text. Here is my code:
ViewController:
#IBOutlet weak var lblToUpdate: UILabel?
Here is my app delegate:
DispatchQueue.main.async{
let storyboard = UIStoryboard(name:"Main", bundle:nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "myVC") as! myViewController
if let text = newText{
myVC.text?.text = text
}
}
I have no errors of any kind but the the label never updates.
Any of you knows why the label never gets updated?
I'll really appreciate your help.
The problem is that this VC
let myVC = storyboard.instantiateViewController(withIdentifier: "myVC") as! myViewController
is not the presented one you have to query the window for it and change it's label as you currently play with another one
its not working because the label doesn't exists in the time, just pass the value to a variable in your next controller and then in some of the methods for example :
viewdidload you can update your label with the value of the variable for example :
DispatchQueue.main.async{
let storyboard = UIStoryboard(name:"Main", bundle:nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "myVC") as! myViewController
if let text = newText{
myVC.theVariable = text
}
}
class NextViewController: UIViewController {
var theVariable = ""
override func viewDidLoad() {
super.viewDidLoad()
lblToUpdate.text = theVariable
}
You shouldn't create new instance because is other viewcontroller, this already exist.
You should access to rootViewcontroller and follow window hierarchy
DispatchQueue.main.async{
if let vcroot = self.window!.rootViewController {
if let text = newText{
vcroot.text?.text = text
}
}
}
This code above is example to refer you question.
If you have navigationcontroller is for example:
DispatchQueue.main.async{
if let navroot = self.window!.rootViewController as? NavigationController{
if let childvc = navroot.childviewcontrollers.first {
if let text = newText{
childvc.text?.text = text
}
}
}
}
If have severals viewcontroller in navigation controller you will access using for loop and check and arrive to the viewcontroller that wish change value property or set values

How to pass data between two ViewController in UIPageViewController

I have two UIViewControllers, A and B, I connect them within a UIPageViewController:
Here is how it looks in the Storyboard:
I don't know how to pass data to B from A.
Well assume you have some class (which you should have provided) like:
class MyModel {
var dataFromFirstController: Any?
var dataFromSecondController: Any?
var sharedData: Any?
}
Now you need a subclass of page view controller which is the one that controls the data so override view did load to create a model:
var myModel: MyModel!
override func viewDidLoad() {
super.viewDidLoad()
self.myModel = MyModel()
}
Now when you generate or fetch view controllers you simply assign the same model to them:
func getFirstViewController() -> UIViewController {
let controller = MyFirstController.generate()
controller.myModel = self.myModel
return controller
}
func getSecondViewController() -> UIViewController {
let controller = MySecondController.generate()
controller.myModel = self.myModel
return controller
}
Now all 3 view controllers share the same model. This is probably the easiest way of doing it but there are very many ways. The cleanest is probably using delegates which would report back to page controller that would then report back to given view controllers.
I had some difficulty finding a solution to this and came up with something myself using delegation. Suggestions are welcome
In the ViewController sending the data, define a delegate as follows:
protocol FirstVCDelegate {
func foo(someData: String)
}
class FirstViewController: UIViewController {
var delegate: FirstVCDelegate?
....
func someMethod() {
delegate?.foo("first VC")
}
}
In the PageViewController set up your View Controllers as follows:
class PageViewController: UIPageViewController, ... {
var myViewControllers = [UIViewController]()
override func viewDidLoad() {
let firstVC = storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
let secondVC = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
firstVC.delegate = secondVC
myViewControllers.append(firstVC)
myViewControllers.append(secondVC)
}
// MARK: - PageVC Delegate / Datasource
....
and finally, the receiving ViewController implements the delegate as follows:
class SecondViewController: UIViewController, FirstVCDelegate {
....
func foo(data: String) { // This method is triggered from FirstViewController's delegate?.foo("first VC")
print(data) // "first VC" will be printed
}
}
Good luck,
Aaron

Accessing elements from another view controller comes up nil

I have an alert view that i made using presentr from GitHub. I used a simple view controller that will overlay over the current view controller. Now i have elements such as a UIImage and a UIlabel from the first view controller that needs to be accessed by the alert view controller. But when I click a unbutton in the alert view controller to access the uiimage and text from uilabel from the firstviewcontroller. Here is the code. Can you show me how I can fix this. I can't segue the data because I'm presenting the view controller and the data I'm trying to access is too much too segue anyway. I keep getting this error "fatal error: unexpectedly found nil while unwrapping an Optional value" overtime i click the save button in alertviewcontroller.
class firstviewcontroller: UIViewController{
var photos2: [ImageSource]?
#IBOutlet weak var Label: UIlabel!
#IBOutlet weak var Image: UIImage!
}
class alertviewcontroller: UIViewController{
#IBAction func Save(_ sender: Any) {
dismiss(animated: true, completion: nil)
let storyboard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let firstViewController: firstviewcontroller = storyboard.instantiateViewController(withIdentifier: "firstviewcontroller") as! firstviewcontroller
if let image = firstViewController.Image {
imageview.image = image
}
if let label = firstViewController.Label {
label.text = Label.text
}
}
}
The biggest mistake you're making is that you are creating a new instance of firstviewcontroller instead of just accessing the current one.
class alertviewcontroller: UIViewController{
#IBAction func Save(_ sender: Any) {
dismiss(animated: true, completion: nil)
let storyboard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let firstViewController: firstviewcontroller = storyboard.instantiateViewController(withIdentifier: "firstviewcontroller") as! firstviewcontroller // This is the mistake
if let image = firstViewController.Image {
imageview.image = image
}
if let label = firstViewController.Label {
label.text = Label.text
}
}
}
What you should do instead is access the presentingViewController since you presented the alertviewcontroller using the present function
The code would look like this
class alertviewcontroller: UIViewController{
#IBAction func Save(_ sender: Any) {
dismiss(animated: true, completion: nil)
let storyboard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
// NOTE: only do implicit unwrapping `as!` if you're sure that the value you're unwrapping is not `nil` or it is of the correct `data type` cause it might cause your app to crash
let firstViewController: firstviewcontroller = self.presentingViewController as! firstviewcontroller
if let image = firstViewController.Image {
imageview.image = image
}
if let label = firstViewController.Label {
label.text = Label.text
}
}
}
Tip: Please review Swift coding guidelines since there are some minor mistakes regarding your naming of methods, variables, and classes.
Might I am wrong but after review your code I have found this messing statement
let firstViewController: firstviewcontroller = storyboard.instantiateViewController(withIdentifier: "firstviewcontroller") as! firstviewcontroller
In this statement you are creating a new instance of firstviewcontroller not accessing already created instance of firstviewcontroller.
Next, you said
I used a simple view controller that will overlay over the current
view controller
So I have assumed you didn't present or push alertviewcontroller on current viewController, on that behave I suggest you the following solutions
In alertviewcontroller get the instance of topViewController by using this method
class func topViewController(base: UIViewController? = (UIApplication.shared.delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
then you can transfer your data from alertviewcontroller to topViewController.
Next solution is that you can pass values from alertviewcontroller to firstviewcontroller by using delegates.

How to send data back by popViewControllerAnimated for Swift?

I need to send some data back from secondView to First View by popView.
How can i send back the data by popViewControllerAnimated?
Thanks!
You can pass data back using delegate
Create protocol in ChildViewController
Create delegate variable in ChildViewController
Extend ChildViewController protocol in MainViewController
Give reference to ChildViewController of MainViewController when navigate
Define delegate Method in MainViewController
Then you can call delegate method from ChildViewController
Example
In ChildViewController: Write code below...
protocol ChildViewControllerDelegate
{
func childViewControllerResponse(parameter)
}
class ChildViewController:UIViewController
{
var delegate: ChildViewControllerDelegate?
....
}
In MainViewController
// extend `delegate`
class MainViewController:UIViewController,ChildViewControllerDelegate
{
// Define Delegate Method
func childViewControllerResponse(parameter)
{
.... // self.parameter = parameter
}
}
There are two options:
A) with Segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
let goNext = segue.destinationViewController as ChildViewController
goNext.delegate = self
}
B) without Segue
let goNext = storyboard?.instantiateViewControllerWithIdentifier("childView") as ChildViewController
goNext.delegate = self
self.navigationController?.pushViewController(goNext, animated: true)
Method Call
self.delegate?.childViewControllerResponse(parameter)
If you want to send data by popping, you'd do something like:
func goToFirstViewController() {
let a = self.navigationController.viewControllers[0] as A
a.data = "data"
self.navigationController.popToRootViewControllerAnimated(true)
}
Extending Dheeraj's answer in case your ViewController is not first VC in the stack, here is the solution:
func popViewController() {
guard let myVC = self.navigationController?.viewControllers.first({ $0 is MyViewController }) else { return }
myVC.data = "data"
self.navigationController?.popViewController(animated: true)
}
However, this solution will break if you have 2 or more than 2 MyViewController in the stack. So, use wisely.
Answer given here is a little complex, quite simple to just use UINavigationControllerDelegate
class FirstNavigationController: UIViewController {
var value: String?
}
class SecondNavigationController: UIViewController, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
guard let vc = navigationController.topViewController as? FirstNavigationController else { return }
vc.value = "Hello again"
}
}
self.navigationController?.popViewController(animated: true)
let vc = self.navigationController?.viewControllers.last as! MainViewController
vc.textfield.text = "test"
this popviewcontroller solutions

Resources