Set value in random ViewController (random segue) - ios

I have 12 ViewControllers. My code gives a random segue to one of these ViewControllers (1-12)
let segues = ["View1", "View2", "View3", "View4", "View5", "View6", "View7", "View8", "View9", "View10", "View11", "View12"]
let index = Int(arc4random_uniform(UInt32(segues.count)))
let segueName = segues[index]
self.performSegueWithIdentifier(segueName, sender: self)
.
Now, I want to change a variable in the random ViewController that has been chosen (var firstSegue = false) but I can't figure out how?
.
Could someone change this into something that will work?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as UIViewController
destinationVC.firstSegue = true
}

Make a protocol with the firstSegue property and extend that to all of your view controllers. Then use as? TheProtocolYouMade instead of as UIViewController
Or use a common sub-class.
EDIT: sample code
Make a protocol with the firstSegue property
protocol P {
var firstSegue: Bool { get set }
}
extend that to all of your view controllers.
extension YOURViewController1: P {
}
It is assumed that YOURViewController1 has a var firstSegue: Bool in it. Now that property is how YOURViewController1 conforms to protocol P. Do this for all view controllers that have that property.
Now you can write this code
if let asP = segue.destinationViewController as? P {
// you can access asP.firstSegue here
}

As this controller has property that's not from standard UIViewController, than it's some custom controller, so you can cast it and than fill this value. As well you can use KVC - which is not really safe solution.

Related

iOS - Sending sender-linked variables through a segue

Through using a loop I've programmatically created a bunch of UIButtons. Each of these triggers the same segue to another View Controller which is supposed to display some information related to the button.
For this to work, I need to send at least one attribute/variable linked to the specific button tapped through the segue.
One option I tried was creating a new UIButton class to hold the attribute.
class statButton: UIButton {
var buttonIndex = Int()
}
If this even works, how would I access that data here:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "StatDetailSegue" {
if let destinationVC = segue.destination as? StatDetailViewController {
destinationVC.statTitle = // need to access here
}
}
}
The variable I need to send is a value in a dictionary
You have to make sure that the sender is a statButton, and then cast its buttonIndex property to a proper String:
if let destinationVC = segue.destination as? StatDetailViewController,
let index = (sender as? statButton).buttonIndex {
destinationVC.statTitle = String(index) //this is supposing that destinationVC.statTitle expects a String
}
P.S: It is a convention in Swift to have the names of classes with an uppercase first letter:
class statButton: UIButton { ... }

UIView is nil in prepareForSegue method

sorry for confusing title, I didn't know how to make it more meaningfull.
I was implementing information window using this example: github
I have added few labels into popView and linked them to secondaryVieController.
I have amended prepareForSegue method in PrimaryViewController to:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showInfo" {
let info = segue.destinationViewController as! SecondaryViewController
info.adrLabel.text = branch.address
info.nameLabel.text = branch.name
info.telLabel.text = branch.tel
dim(.In, alpha: dimLevel, speed: dimSpeed)
}
}
But when I run it i get error "fatal error: unexpectedly found nil while unwrapping an Optional value"
it is because info.popupView is nil.
But can you explain me how should I set it? And why it is working fine if there are no any additional labels.
Thanks.
The first method where you will be access this
info.adrLabel.text = branch.address
info.nameLabel.text = branch.name
info.telLabel.text = branch.tel
values will be viewDidLoad() of SecondaryViewController.
You need to create variables and assign values in those variables and in the viewDidLoad() you can use those variables to assign the value to your labels.
You are getting nil because you are trying to set values before the view has loaded from your prepareForSegue method. Instead, setup some variables in your SecondaryViewController to hold the values and set up your labels in viewDidLoad:
class SecondaryViewController {
var address: String?
var name: String?
var tel: String?
override func viewDidLoad() {
adrLabel.text = address
nameLabel.text = name
telLabel.text = tel
}
}
Now all you need to do in your prepareForSegue is:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showInfo" {
let info = segue.destinationViewController as! SecondaryViewController
info.address = branch.address
info.name = branch.name
info.tel = branch.tel
dim(.In, alpha: dimLevel, speed: dimSpeed)
}
}
As a general rule you should not try to manipulate another view controller's views directly. That violates the principle of encapsulation. You should consider them private.
Instead, add properties to your destination view controller and set those properties. Then in your destiination view controller's viewWillAppear method install the values from those properties into the appropriate views.
That way if you later change your view controller's views you only have to change the code in one place (the view controller)
(Plus, in situations like your prepareForSegue function, it doesn't work.)

How to override kind of segue in code?

Simply I have one segue of kind Show from FirstViewController to the SecondViewController.
I call it on iPad and iPhone from two places of my FirstViewController. It is ok, but in one case I need to present SecondViewController as a .Popover.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let secondViewController = segue.destinationViewController as? SecondViewController {
if let cell = sender as? UITableViewCell, indexPath = tableView.indexPathForCell(cell) {
let student = fetchedResultsController.objectAtIndexPath(indexPath) as! Student
secondViewController.schoolClass = schoolClass
secondViewController.student = student
} else {
secondViewController.modalPresentationStyle = .Popover
secondViewController.schoolClass = schoolClass
let popoverPresentationController = studentFormViewController.popoverPresentationController
popoverPresentationController!.delegate = self
popoverPresentationController?.permittedArrowDirections = .Up
popoverPresentationController?.barButtonItem = addStudentBarButtonItem
}
}
}
It doesnt work.
Any ideas how to override kind of segue from storyboard in code?
You should use custom segues:
implement custom segue by subclassing UIStoryboardSegue class and override prepare() method. (In prepare() method you implement all required behaviour (controllers animation, presentation and so on depending on your case);
Change you segue type from push to custom in the interface builder, and pick your implementation in class property (like on image below)
Best regards!

Why can't set a variable value of a viewController by using another viewController?

I know this is Duplicate Question, but i didn't find the solution for my problem. I have two controllers. HomeViewcontroller's variable of "myValue"'s value can't be set by using ViewController class.I don't know the issue here but code is working without error.But when i print "myValue" it shows nil.Destination view controller is not the controller that i want to set variable value. Destination view controller is a tab bar controller, so i want to set the variable value of first tab bar and second tab bar view controllers. This is my code :-
import UIKit
class ViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if(segue.identifier == "gotoHome") {
println("1111")
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var viewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
viewController.myValue = 8888
}
}
}
this is the other view controller
import UIKit
class HomeViewController: UIViewController {
var myValue: Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
println(myValue)
}
}
You don't need to instantiate the viewcontroller, as it is already been done, you just need to fetch the viewcontroller for the current segue operation, so your code should look like below
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if(segue.identifier == "gotoHome") {
println("1111")
let controller = segue.destinationViewController as! UITabBarController
let homeController = controller.selectedViewController as !HomeViewController
homeController.myValue = 8888
}
}
Hope it helps.
Change to :
let viewController = segue.destinationViewController as! HomeViewController
You're printing the value of myValue in viewDidLoad which is called when you're instantiating it from your storyboard. You're setting myValue after creating your instance. Try to print myValue in viewWillAppear for example, then it should be set.

Access Container View Controller from Parent iOS

in iOS6 I noticed the new Container View but am not quite sure how to access it's controller from the containing view.
Scenario:
I want to access the labels in Alert view controller from the view controller that houses the container view.
There's a segue between them, can I use that?
Yes, you can use the segue to get access the child view controller (and its view and subviews). Give the segue an identifier (such as alertview_embed), using the Attributes inspector in Storyboard. Then have the parent view controller (the one housing the container view) implement a method like this:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString * segueName = segue.identifier;
if ([segueName isEqualToString: #"alertview_embed"]) {
AlertViewController * childViewController = (AlertViewController *) [segue destinationViewController];
AlertView * alertView = childViewController.view;
// do something with the AlertView's subviews here...
}
}
You can do that simply with self.childViewControllers.lastObject (assuming you only have one child, otherwise use objectAtIndex:).
for Swift Programming
you can write like this
var containerViewController: ExampleViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// you can set this name in 'segue.embed' in storyboard
if segue.identifier == "checkinPopupIdentifierInStoryBoard" {
let connectContainerViewController = segue.destinationViewController as ExampleViewController
containerViewController = connectContainerViewController
}
}
The prepareForSegue approach works, but it relies on the segue identifier magic string. Maybe there's a better way.
If you know the class of the VC you're after, you can do this very neatly with a computed property:
var camperVan: CamperVanViewController? {
return childViewControllers.flatMap({ $0 as? CamperVanViewController }).first
// This works because `flatMap` removes nils
}
This relies on childViewControllers. While I agree it could be fragile to rely on the first one, naming the class you seek makes this seem quite solid.
An updated answer for Swift 3, using a computed property:
var jobSummaryViewController: JobSummaryViewController {
get {
let ctrl = childViewControllers.first(where: { $0 is JobSummaryViewController })
return ctrl as! JobSummaryViewController
}
}
This only iterates the list of children until it reaches the first match.
self.childViewControllers is more relevant when you need control from the parent. For instance, if the child controller is a table view and you want to reload it forcefully or change a property via a button tap or any other event on Parent View Controller, you can do it by accessing ChildViewController's instance and not via prepareForSegue. Both have their applications in different ways.
There is another way using Swift's switch statement on the type of the view controller :
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
switch segue.destination
{
case let aViewController as AViewController:
self.aViewController = aViewController
case let bViewController as BViewController:
self.bViewController = bViewController
default:
return
}
}
I use Code like:
- (IBAction)showCartItems:(id)sender{
ListOfCartItemsViewController *listOfItemsVC=[self.storyboard instantiateViewControllerWithIdentifier:#"ListOfCartItemsViewController"];
[self addChildViewController:listOfItemsVC];
}
In case someone is looking for Swift 3.0,
viewController1, viewController2 and so on will then be accessible.
let viewController1 : OneViewController!
let viewController2 : TwoViewController!
// Safety handling of optional String
if let identifier: String = segue.identifier {
switch identifier {
case "segueName1":
viewController1 = segue.destination as! OneViewController
break
case "segueName2":
viewController2 = segue.destination as! TwoViewController
break
// ... More cases can be inserted here ...
default:
// A new segue is added in the storyboard but not yet including in this switch
print("A case missing for segue identifier: \(identifier)")
break
}
} else {
// Either the segue or the identifier is inaccessible
print("WARNING: identifier in segue is not accessible")
}
With generic you can do some sweet things. Here is an extension to Array:
extension Array {
func firstMatchingType<Type>() -> Type? {
return first(where: { $0 is Type }) as? Type
}
}
You can then do this in your viewController:
var viewControllerInContainer: YourViewControllerClass? {
return childViewControllers.firstMatchingType()!
}
you can write like this
- (IBAction)showDetail:(UIButton *)sender {
DetailViewController *detailVc = [self.childViewControllers firstObject];
detailVc.lable.text = sender.titleLabel.text;
}
}

Resources