Is it okay to have a lot of segues between view controllers? - ios

So I'm quite new to iOS developing and have decided to venture down the swift path. I'm now working with multiple view controllers and passing data between, and what I'm wondering is if it is bad practice to have multiple (like 9-15) segues between 2 view controllers?
Basically what I have is a normal VC with 9 different buttons. Now, what I want is for each button to send different data to the subsequent tabbed VC instead of having a different VC for each button/catagory.
it would look something like this :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let tabBarController = segue.destinationViewController as! UITabBarController
let svc = tabBarController.viewControllers![0] as! InfoViewController
let svc2 = tabBarController.viewControllers![1] as! DIYViewController
if segue.identifier == "wind" {
svc.titleString = "Wind Power... what a powerful thing"
} else if segue.identifier == "geothermal" {
//send info about geothermal
} else if segue.identifier == "hydroelectric" {
//send infor about hydroelectricity
} else if segue.identifier == "" {
//code
} else if segue.identifier == "" {
//code
} else if segue.identifier == "" {
//code
} else if segue.identifier == "" {
//code
}
}
is there a more conventional way to accomplish this?

You should have only one segue, and pass the data in prepareForSegue(_:sender:). E.g. In your first view controller, you pass the variable:
class ViewController: UIViewController {
var myVariable: String!
#IBAction func button1Action(sender: AnyObject) {
myVariable = "Hello"
}
#IBAction func button2Action(sender: AnyObject) {
myVariable = "Hola"
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue == "MySegue" {
let secondViewController = segue.destinationViewController as! ViewController
secondViewController.myVariable = myVariable
}
}
}
And in your second view controller, you can do whatever you want with your variable:
class SecondViewController: UIViewController {
var myVariable: String!
override func viewDidLoad() {
title = myVariable
}
}

You can create a NSObject subclass (e.g. MyAtmospherologyObject) and put all the needed properties within to store all the info for all 9 categories you might needed.
Then give it a type property and use enum for the only segue to determine what it was getting and how it should handle the rest of the data.
After this, you only need to send one object, which is the MyAtmospherologyObject. This could be useful as you can pass around the object anywhere as a whole once you initialized it, and reuse it from there.
e.g. Objective-C (I know it's not Swift, but you get the idea.)
MyAtmospherologyObject.h
//
// MyAtmospherologyObject.h
//
#import Foundation;
typedef NS_ENUM(NSUInteger, MyAtmospherologyType) {
MyAtmospherologyGeothermalType = 0,
MyAtmospherologyHydroelectricType
};
#interface MyAtmospherologyObject : NSObject
#property (nonatomic) MyAtmospherologyType type;
#property (nonatomic, strong) NSString *mySting;
- (MyAtmospherologyType)type;
#end
MyAtmospherologyObject.m
//
// MyAtmospherologyObject.m
//
#import "MyAtmospherologyObject.h"
#implementation MyAtmospherologyObject
- (instancetype)initWithType:(MyAtmospherologyType)aType
{
self = [super init];
if (self) {
_type = aType;
}
return self;
}
#pragma mark - NSKeyedArchiver
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
_type = [decoder decodeIntegerForKey:#"type"];
_mySting = [decoder decodeObjectForKey:#"mySting"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInteger:_type forKey:#"type"];
[encoder encodeObject:_mySting forKey:#"mySting"];
}
#pragma mark - Methods
- (MyAtmospherologyObject)type
{
return _type;
}
#end
MyAtmospherologyViewController.m
// ...
#import "MyAtmospherologyObject.h"
#implementation MyAtmospherologyViewController
// ...
- (IBAction)buttonAction:(id)sender
{
MTKGSJournalQuestionnaireObject *object = [[MTKGSJournalQuestionnaireObject alloc] initWithType:MyAtmospherologyGeothermalType];
object.myString = #"aString";
[self performSegueWithIdentifier:#"MySegueIdentifier"
sender:object];
}
#pragma mark - UIStoryboardSegue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
MyAtmospherologyObject *object = (MyAtmospherologyObject *)sender;
MyAtmospherologySegueViewController *vc = (MyAtmospherologySegueViewController *)segue.destinationViewController;
switch (object.type) {
case MyAtmospherologyGeothermalType:
vc.navigationItem.title = object.myString;
break;
case MyAtmospherologyHydroelectricType:
vc.navigationItem.title = object.myString;
break;
default:
break;
}
}
// ...
#end

Related

How to set a delegate in Swift

I want to send my UserModel with all user informations from a ViewController (ShowUserViewController) to another ViewController (ChatViewController) with a delegate but its not working.
In my ShowUserViewControllers user are all informations I want to send to the ChatViewController.
var user: UserModel?
In my ChatViewController I have the following declaration where I want to send my datas:
var currentUser: UserModel?
Here my protocol:
protocol UserInfoToChatID {
func observeUserID(user: UserModel)
}
Here I prepare the segue and set delegate by tapping the button:
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
var delegate: UserInfoToChatID?
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
delegate?.observeUserID(user: user!)
}
At last I call the delegate in my ChatViewController:
extension ChatViewController: UserInfoToChatID {
func observeUserID(user: UserModel) {
self.currentUser = user
performSegue(withIdentifier: "UserInfoToChatVC", sender: self)
}
}
If you need to pass data from one ViewController to another, you don't have to use delegates for this. You can just pass this data as sender parameter of performSegue method:
performSegue(withIdentifier: "UserInfoToChatVC", sender: user!)
then in prepare for segue just downcast sender as UserModel and assign destination's currentUser variable
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = sender as! UserModel
}
}
But in your case you actually don't have to pass user as sender. You can just assign destination's currentUser variable as ShowUserViewController's global variable user
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = user!
}
}
2 things:
first, if you just want to pass data from one viewController to other viewController you don't need to use delegate pattern, just pass the object to the next viewController on prepare form segue.
second, if you want to implement the delegate pattern you should have one viewController than call to the delegate and the other implement the functions.
example:
protocol ExampleDelegate: class {
func delegateFunction()
}
class A {
//have delegate var
weak var delegate: ExampleDelegate?
// someWhere in the code when needed call to the delegate function...
delegate?.delegateFunction()
}
Class B: ExampleDelegate {
func delegateFunction() {
// do some code....
}
//when you move to the next viewControoler(to A in that case)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AClass" {
if let vc = segue.destination as? A {
vc.delegate = self
}
}
}
To pass the UserModel object forward, from ShowUserViewController to ChatViewController, you should use something called Dependency Injection:
So you'll do something like this inside ShowUserViewController:
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "UserInfoToChatVC", sender: nil)
}
Note: The sender parameter should be the object that initiated the segue. It could be self, i.e. the ShowUserViewController object, but I'd advise against passing the UserModel object, because that object did not initiate the segue, and has nothing to do with navigation at all. It should be injected inside the Destination Controller later on.
In the same file, override the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
I believe you've mostly done this part right, but you may need to communicate back from ChatViewController to ShowUserViewController.
In that case, you can and should use Delegation.
Create something like this inside ShowUserViewController:
protocol ChatViewControllerDelegate: class {
func didUpdateUser(_ model: UserModel)
}
class ChatViewController: UIViewControler {
var user: UserModel?
weak var delegate: ChatViewControllerDelegate?
/* more code */
func someEventHappened() {
delegate?.didUpdateUser(self.user!)
}
}
Finally, there is an additional line to be added to the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
// Add this line...
chatVC.delegate = self
}
}
And specify that the ShowUserViewController implements the ChatViewControllerDelegate protocol, then override the didUpdateUser(_:) method:
func didUpdateUser(_ model: UserModel) {
// Some code here
}

Cannot assign value of type String to type UILabel

I have marked the part of my code where the problem is, it is commented out. The error message is:
Cannot assign value of type String! to type UILabel!.
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.messageLabelDos = sendingText
}
}
}
The label it should be changing is in my DetailViewController and it is a label. The code above is from my original ViewController. How can I make this work?
More code to put in context:
if metadataObj.stringValue != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("SendDataSegue", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.viaSegue = sendingText
}
}
}
You need to pass the String instead of setting text to label, because when you correct it and set like this sendToDetailViewController.messageLabelDos.text = sendingText, you will get nil error because messageLabelDos is not initialize yet, so try like this. Create one string instance in DetailViewController and use that inside prepareForSegue for passing String and then use that String instance in viewDidLoad to assign Label to text.
class ViewController: UIViewController {
//Your other methods
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.messageDos = sendingText
}
}
}
}
Inside DetailViewController
var messageDos: String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.messageLabelDos.text = messageDos
}

Pass a parameter to prepareForSegue from function

I have to functions, both of them trigger performSegueWithIdentifier with the same segue. But depending of which function was called I need to pass different parameters in prepareForSegue.
Some thing like
func first() {
// do some stuff
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func second() {
// do some stuff
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "My segue" {
let destination = segue.destinationViewController as! MyController
if functionFirstWasCalled {
destination.property = value
} else if functionSecondWasCalled {
destination.property = anotherValue
}
}
}
Surely, I can do this by setting booleans from second() and first() and then checking them in prepareForSegue - but maybe there is some more elegant way to do this ?
In objective -c you would do:
[self performSegueWithIdentifier:#"segueNAme" sender:#"firstMethod"];
and you can access this message in the prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([sender isEqualToString:#"firstMethod"]) {
//firstMEthod called the segue
}
}
The swift equivalent I think would be:
self.performSegueWithIdentifier("segueNAme", sender: "firstMethod")
and
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject) {
if (sender == "firstMethod") {
//firstMEthod called the segue
}
}
My suggestion would be to instead of sending a plain string , send a dictionary type object that contains the methodName, className and some other params useful for future debugging.
All you have to do is send a flag by sender attribute, something like this:
func first() {
performSegueWithIdentifier("My segue", sender:true)
}
func second() {
// do some stuff
performSegueWithIdentifier("My segue", sender:false)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "My segue" {
let destination = segue.destinationViewController as! MyController
let isFirstFunctionCalled = sender as! Bool // cast sender to bool
if isFirstFunctionCalled {
destination.property = value
} else {
destination.property = anotherValue
}
}
}
Simply set parallel properties in this View Controller which you then pass to the destination View Controller based on the function called. i.e:
var a = ""
var b = 0
func first() {
// do some stuff
a = "first function determined this variable."
b = 1
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func second() {
// do some stuff
a = "second function determined this variable."
b = 182
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "My segue" {
let destination = segue.destinationViewController as! MyController
if functionFirstWasCalled {
destination.property = a
} else if functionSecondWasCalled {
destination.property = b
}
}
}

Is it possible to pass a enum through segue in swift

I have this prepareForSegue in my first ViewController with a enum in it
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
enum TypeOfSegue {
case Edit, Create
}
if let DestViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "EditItem" {
let edit = TypeOfSegue.Edit
}
if let DestViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "CreateNewItem" {
let create = TypeOfSegue.Create
}
}
I want to pass the constants edit or create to my DetailViewController. If possible, how can I do this and under what variable will the TypeOfSegue be saved in DetailViewController?
You can define a mode property in your DetailViewController
class DetailViewController:UIViewController {
var mode: TypeOfSegue!
}
Next you can populate it
enum TypeOfSegue {
case Edit, Create
}
class ListViewController:UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "EditItem" {
destViewController.mode = .Edit
}
if let destViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "CreateNewItem" {
destViewController.mode = .Create
}
}
}
Update
You can also implement the prepareForSegue method this way
enum TypeOfSegue: String, CustomStringConvertible {
case Edit = "EditItem", Create = "CreateNewItem"
var description: String { return self.rawValue }
}
class ListViewController:UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let
destViewController = segue.destinationViewController as? DetailViewController,
identifier = segue.identifier,
segueType = TypeOfSegue(rawValue: identifier) else { fatalError("Wrong segue") }
destViewController.mode = segueType
}
}

UITraitCollection - swift to objective-C

I'm trying to convert swift to objective-C. Here is my code below:
Swift
import UIKit
protocol TraitCollectionOverridable {
func preferredTraitCollection() -> UITraitCollection?
}
class CustomNavigationController: UINavigationController {
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection? {
guard let conformingViewController = childViewController as? TraitCollectionOverridable else {
return super.overrideTraitCollectionForChildViewController(childViewController)
}
return conformingViewController.preferredTraitCollection()
}
}
Objective-C
header file
#protocol TraitCollectionOverridable <NSObject>
- (UITraitCollection *) preferredTraitCollection;
#end
#interface CustomNavigationController : UINavigationController
#property (nonatomic, weak) id <TraitCollectionOverridable> traitsDelegate;
#end
.m file
#implementation CustomNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
[self.traitsDelegate preferredTraitCollection];
// Do any additional setup after loading the view.
}
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
} else {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
}
}
#end
My guess is overrideTraitCollectionForChildViewController is not converted properly. Any help on that would be great.
My guess is overrideTraitCollectionForChildViewController is not converted properly.
The Obj-C & Swift versions of overrideTraitCollectionForChildViewController aren't equivalent. Here is the corrected code:
Obj-C
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if( [childViewController conformsToProtocol:#protocol(TraitCollectionOverridable)] ) {
return [(NSObject<TraitCollectionOverridable>*)childViewController preferredTraitCollection];
} else {
return [super overrideTraitCollectionForChildViewController:childViewController];
}
}
Swift
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection? {
guard let conformingViewController = childViewController as? TraitCollectionOverridable else {
return super.overrideTraitCollectionForChildViewController(childViewController)
}
return conformingViewController.preferredTraitCollection()
}

Resources