Possible to perform segue without the use of StoryBoard? - ios

I have been creating my Swift project completely programmatically thus far. I have created detailed Navigation Controllers and Table View Controllers without touching the StoryBoard (SB) once. I have come to a point where I would like to pass data from a TableViewCell click onto another ViewController, however, I believe that I need to use a segue and create/identify it inside of the SB. Since this application has become pretty complex over time, it has become quite difficult to mimic all of the view controllers inside of the SB, and creating any changes inside of the SB is not reflecting any changes inside of the Views on the simulator (I have paired the views inside of the SB to their respective class, yet nothing is working. Even the segue is not being recognized when the identifier matches up). Therefore, I have a couple of questions.
Is it possible to perform a segue programmatically? In other words, is it possible to pass data from one view controller to another without touching the story board?
Is there a simple guide or technique one can follow in order to mimic their code inside of the StoryBoard? With a simple application, it shouldn't be too difficult. However, is there any other way in order to portray the application in the StoryBoard if someone has been creating it completely programmatically?

Interface Builder (IB) is just a GUI for programmatic development. Anything you can do in IB you can definitely do programmatically but not everything you can do programmatically you can do in IB, because IB is just for interface building—and just some interface building, not all.
segue is just IB terminology. In iOS, you can only display a view controller three ways: push (via UINavigationController), present (via a presentation object vendor UIViewControllerTransitioningDelegate), or show (a more generic approach to displaying a view controller).
Since you're new to programmatic iOS development (the best kind, IMO), a quick 101:
class ProgrammaticViewController: UIViewController {
override func loadView() {
// do not call super.loadView()
// most IB developers don't even know this method exists
// because this is where IB does its work
// add all of your view objects here (scroll views,
// table views, buttons, everything)
setView()
addTableView()
...
}
override func viewDidLoad() {
super.viewDidLoad()
// do call super.viewDidLoad(), however
// do your post-view setup here, like adding observers
// or fetching data
// this method is called after the entire view
// has been loaded into memory so consider that
}
// other lifecycle events that come after viewDidLoad where you
// can perform last-second work include viewDidLayoutSubviews(),
// viewWillAppear(), viewDidAppear(), etc.
deinit {
// do any cleanup here like deactivating timers, removing
// observers, etc.
}
// MARK: Methods
func setView() {
// if you're creating your view controller programmatically,
// you must create the view controller's actual view property
// and it must be done before adding any subviews to the
// view controller's view
view = UIView()
view.frame = UIScreen.main.bounds
view.backgroundColor = UIColor.white
}
}
If you are using auto layout (and you probably should be), you don't always need to explicitly set the frame of the view with view.frame = UIScreen.main.bounds. If it's the root view controller of the app, it's not needed—the window owns that view controller and it will set the frame. If you've then put a UINavigationController in the root and you're using auto layout, none of the view controllers you push to ever need their frame set as well. Therefore, the only real time you need to explicitly set the view's frame using something like view.frame = UIScreen.main.bounds is when you present a view controller modally. This is because a modally-presented view controller is not owned by the window or a navigation controller; it exists temporarily inside a transient container view. In this case, you must set its frame.
Passing data forward programmatically is simpler than it is for IB developers. Just instantiate a view controller, inject one of its (non-private) properties with a value, and push, present, or show it. Here's what a UITableView delegate for didSelectRowAt may look like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let p = indexPath.row
let detailViewController = SomeDetailViewController()
detailViewController.someProperty = searchResults[p]
navigationController?.pushViewController(detailViewController, animated: true)
}
And this is obviously doable because you've created that property in that view controller and not given it a private access modifier.
class SomeDetailViewController: UIViewController {
var dataObject: SomeType? // injectable
private var notInjectable: SomeType?
}
UPDATE: IB is clearly not the future of iOS development as far as Apple is concerned, with the introduction of SwiftUI, which is another way of programmatically developing iOS. Personally, I hate GUI programming and I'm happy to see it slowly phase out.

Is it possible to perform a segue programmatically?
Sure. UIStoryboardSegue has initializers that you can use to create a segue, and it has a perform() method that you can call to make the segue do it's thing. But segues are mostly a tool that makes it easier to transition between scenes in a storyboard, so there's little reason to want to do that if you're not using storyboards in the first place.
In other words, is it possible to pass data from one view controller to another without touching the story board?
Again, yes. Storyboards didn't even exist until iOS 5, and you can bet people were writing apps with transitions between view controllers well before that. The answer you've already gotten about how to do that is fine. More generally, though, view controllers are just objects, and all that one needs to send information to another is a valid reference to the destination controller.
However, is there any other way in order to portray the application in the StoryBoard if someone has been creating it completely programmatically?
If you mean that you want to switch over to using storyboards, then yes, there are some good ways to do that. If you mean that you want to somehow represent what your app is doing programmatically inside a storyboard but continue to do everything programmatically, then no, there's no way to do that (and really no point in doing it).
Transitioning to storyboards doesn't have to be an all or none kind of thing. You can model just one view controller in a storyboard and load it as needed using init(name:bundle:) (to initialize the storyboard itself) and instantiateViewController(withIdentifier:) (to load a particular view controller), and then just use that view controller the same way you do now. All the code that you use to set up that view controller and it's view hierarchy can then be removed, since you're now loading it from the storyboard instead. Once that's working, repeat the process for other view controllers, expanding your storyboard and excising the setup code as necessary.

Related

Reusing view on different scenes in IB or programatically

I've a view which I want to reuse on other scenes in IB. This view contains user details such as name, avatar, description, few buttons etc. These views are exactly same and have same elements inside them. Right now it's pain to copy them across scenes, fix the constraints and then code the same elements over and over again. It's not quite productive and it's time consuming. Ideally I want to see these views on IB so I don't want to code do everything programmatically without any visual. Is there a way we can make it in better way so that I can just reuse it?
Not only is it pain, it is poor design to rebuild the same component. I would create a xib file, create the backing swift or obj-c files. Wire up all the outlets as you would normally.
In each view controller or view that you need it, initialize it and add it as a subview.
In swift, I would do something like:
if let subview = Bundle.main.loadNibNamded("SpecialView", owner: self, options: nil)?.first as SpecialView {
// set constraints
containerView.addSubView(subview)
}
Yes you can. I've done this recently. I started in a VC that was a child of a NavigationController and pushed a view onto the view stack that was outside of the NavigationController's scope so the new view did not have the NavigationBar up top.
Assuming you are using a storyboard, you'll create the view with all of it's actions and outlets and link them up to its own class file. Also give the storyboarded view an ID. Then you need to define the controller as a constant and push it to your view stack when you want it used.
// define the controller
let controller = self.storyboard!.instantiateViewController(withIdentifier: "YourCustomControllerID") as! YourCustomController
// if you want to pass data to the new controller
// first you must define some class variables in that class then you do this:
controller.image = whateverImage
// then you push it to the stack like this
self.navigationController!.pushViewController(controller, animated: true)
To dismiss that view, you call dismiss(animated: true, completion: nil) in YourCustomController
Yes, there are a few.
Custom views are loaded as cnbecom said, using LoadNibNamed. Owner is a proxy class, meaning it just represents "anything". Owner is like a connection that is fitted but not made until you create the class, if that makes sense.
Another method is to create the custom view in code, but instead of having a separate nib, you just re-create the view and change the type in the main Xib/Container Xib. You will have to copy/paste the nib, and that's made easier by embedding it into a view before copying. Even with constraints, this method is easier. Hook up the outlets in the containing Xib.
Third is pure code. This is one primary reason constraints are just extra work, no benefits.
Fourth, is to load the Nib but inside a container. Basically it loads itself into its own type via the AwakeFromNib method. You then have to hook up the outlets to the inner Xib realizing that Owner changed. This method is useful if you can visualize what is inside the white square.
Also I never use constraints. The few times it would be helpful, it's still faster to write a basic single-line of frame x,y code in layoutSubviews or similar. Custom views and manually using layoutSubviews will save you lots of time. Springs and struts are extremely powerful, and iOS lacks the CSS media queries for full layout-changes, just has ways to move constraints around (which modifies existing layout, not helpful if changing menu entirely for two different device types).
Create different xibs for tablet, then write a loading func that tests if on tablet and appends a prefix, will give you an amazing system once you get custom loading down.
Anyway, enjoy! I believe this question is a slight duplicate, as your marker was the frustration with auto-layout, but your real question is "How do I load custom views in Swift", which has been answered many.
There are two ways to that:
Programatically (or writing code might be involved)
create a custom view and include all the interface setup you need including the data and add it to each controller you want it to be shown
Take the advantage of Container view for loading the other content in another controller as in this Github repo
Here is what I did in the second example:
Create a base controller that holds the view that I want to always be shown
Add a view and customize it as required
Add a Container View in the same controller
Add a NavigationController and embed it in the Container View

How to remove custom view from superview by pushing button in custom view?

I want to display my own custom view whenever the cell of the view controller's table view is tapped in my iOS app. And I also have to dismiss the custom view when an user taps a button put inside the custom view.
However, while I was able to display the custom view when the cell is tapped by an user, I cannot dismiss the custom view by tapping the button inside the custom view - it's crashed by the error: unrecognized selector sent to instance....
So I wonder what is the correct way to put the button inside the custom view and connect the #IBAction to it in order to dismiss it. Here's what I did:
Add #IBAction connection between the custom view's .xib and its .swift file and call self.removeFromSuperview() from within the method - this makes the app crash with the error message above.
Add #IBAction connection to my view controller that is called to display the custom view (i.e. one that has UITableView) - this cannot be done because when I tried to create the connection, the Xcode doesn't react to the control-drag behaviour.
Also, if I understand it correctly, I cannot add the custom view's xib to the storyboard file because the custom view is not created initially; it's instantiated by tapping the cell on runtime.
So how can I dismiss the custom view by tapping the button? Where should I write the code (i.e. in custom view's .swift file or the original VC's .swift file)?
UPDATE
When I added an #IBOutlet connection from the button on the custom view's xib to the custom view's .swift file, and tried to println() from within the view controller such as (println(customView.dismissButton)), then it also crashed due to the error: this class is not key value coding-compliant for the key dismissButton.. So I might be better off to just add the gesture controller and make any taps on the window react to dismiss the custom view... It's pretty disgusting.
UPDATE 2
#Caroline's zip file in the comment section is exactly what I wanted to do, but instead of creating and instantiating the custom view all from within code, I want to create the UI on xib, create a #IBAction connection between the components on the xib and my code, and finally unarchives it from within code to use.
Today, you should almost certainly be using a container view for this.
Then it is trivial to hide it, animate it, slide it around, send messages to it - whatever.
https://stackoverflow.com/a/23403979/294884
"Thanks. But do I have to use another view controller just in order to do it?"
Yes, of course. You should definitely use a container view. It's the only way to go for years now. Just drag one on in Storyboard.
Pretty much everything is a container view in iOS now. Every little thing. It's "the" paradigm of the day.
You could put your custom view inside a view controller and use segues to load the custom view.
This means that you could have your custom view in the storyboard (under its new view controller), and have a segue from the table view cell to the new view controller, and an unwind segue from the new view controller to the original view controller.
This is an Objective-C tutorial, but may help with the Storyboard concepts:
http://www.raywenderlich.com/50308/storyboards-tutorial-in-ios-7-part-1
Edit:
Depending on what the view is actually doing, the normal way to bring up views for a UITableViewCell detail is to have the detail view inside a separate view controller and link via a segue.
I don't really know why your app is crashing with your code - it really depends on how the Storyboard is set up. If you were to do it in code, you could do it something like this:
class ViewController: UIViewController {
var greenView = UIView(frame: CGRect(x:100,y:500,width:200,height:200))
var greenButton = UIButton(frame: CGRectMake(20, 20, 80, 40))
override func viewDidLoad() {
super.viewDidLoad()
greenView.backgroundColor = UIColor.greenColor()
greenView.addSubview(greenButton)
greenButton.setTitle("Close Me", forState: .Normal)
greenButton.addTarget(self, action: "hideGreenView", forControlEvents: .TouchUpInside)
}
func hideGreenView() {
self.greenView.removeFromSuperview()
}
#IBAction func greenview(sender: AnyObject) {
self.view.addSubview(greenView)
}
}
That code assumes you have a button linked to greenview() on your storyboard, but the rest of it isn't on the storyboard.
You also course create the view on the storyboard, and have it as a hidden view, and then in the functions hide or unhide. If you want to animate the view into position, you could do that with UIView animations.
You also may be having a problem if your UIViewController is a UITableViewController. You could have a UITableView inside a UIViewController with the UIViewController being the delegate and datasource, but this seems like "smelly" code to me (depending on what you are actually trying to achieve, of course).
Tested this code using XCode 8 and Swift 3
To Add Custom View to SuperView use:
self.view.addSubview(myView)
To Remove Custom View from Superview use:
self.view.willRemoveSubview(myView)

Accessing linked Segues created in a Storyboard

I am trying to create a class that is similar in functionality to the UITabBarController, but with some fundamentally different functionality. It is called a dropdownViewController and has a primary content view with a UITabBar-like interface at the top of the screen that allows for other UIViewControllers to be modally presented and dismissed over this primary viewController.
I would like this class to be able to be set up using the storyboard to some extent, and I have created a custom Segue that connects my dropDownViewController class with each of its child viewControllers.
My current solution is to assign identifiers to each of the Segues that are then stored in array within the dropdownViewController. I can call the segues programmatically using the performSegueWithIdentifer: method, but this solution isn't as flexible or intuitive as I would like to to be.
Right now, all the custom Segues that I have setup are connected to the "manual" triggered segue in the storyboard connections panel for the dropdownViewController. (I would put screenshots but this is my first post)
However, I want to mimic the functionality of the UITabBarController class, which has an alternate triggered segue in the storyboard connections panel called viewControllers that each of its child views are assigned to. Unless there are some compile-time macros handling these story board interactions, I assume that the UITabBarController uses these connections to determine what it's view controllers are. However, I can't figure out how to setup this functionality with my own class
After searching around for a solution, it seems likely that this is functionality Apple kept for its own use and is limited to their own classes as a feature in Xcode, but if anyone has solutions or ideas it would be greatly appreciated.
I haven't tried this, but I think you should be able to do it with your own custom segues. In the perform method, you would just add the destination view controller to the source view controller's (DropDownViewController) array of view controllers. In the DropDownViewController's viewDidLoad method (or maybe in an initializer or awakeFromNib, not sure which is most appropriate), you would execute all these segues so that they run right after the controller is loaded like is done for a tab bar controller.

Component reuse within Storyboard

I am new to iOS development, with a couple of years of Android experience. I started directly with XCode 5 and the Storyboard paradigm. I like the visual approach to sketching out the flow of an app, but IMHO, it does not really force component reuse, or maybe I do not know how to do it.
I have an actual problem, which is the following: Instead of the typical master-detail approach, I have a situation, in which clicking on a TableView cell forces a push to another TableView, which looks the same and behaves the same way. There are two specific types of a TableViewCell that I want to reuse across the whole application, so I can't just duplicate the first TableViewController several times. I need changes in one type of TableViewCell to be affected everywhere, same for the look and behaviour of the Tableview. Sort of like reusing a component, you get the picture I hope.
I tried creating a custom TableView and TableViewCell in a separate xib file, connecting it to a custom controller class. Yet, when I want to reuse that controller class in the storyboard, I can't make a segue from the cell to the next one, because only view controllers are displayed, but no views inside.
Maybe, I am doing it all wrong. Perhaps, I should make a single controller, and force it to seque to itself if possible, no idea.
What would you do?
You can do programatic Segue from the didselected.....
#import "someVC.h"
Then when you want to Segue to the new VC
// Create a VC and remember to set the Storyboard ID of someVC (example someVCID)
someVC *newView = [self.storyboard instantiateViewControllerWithIdentifier:#"someVCID"];
// Can't remember how this works but you can change the Transition method
// newView.modalTransitionStyle = UIModalTransition;
// If you want to pass data, setup someArray in the someVC .h with #property (nonatomic, strong) NSArray *someArray;
newView.someArray = MyLocalArray;
// Animated or not
[self presentViewController:newView animated:NO completion:nil];
If what you're trying to do is performing a segue on the same UITableView you can check this answer by Rob
I'll report what it contains for completeness:
If you're using dynamic cell prototypes, you obviously can do a segue
from the table view cell to the controller without any problem.
When you make your segue, you end up with:
But let's imagine for a second that there's some reason that doesn't
work for you, e.g. you could not have a segue from the cell (for
example, you need to invoke segue from didSelectRowAtIndexPath, not
upon selecting the cell). In this case, you couldn't use that previous
technique.
There are a couple of options in this case:
As pointed out by Chris, if only supporting iOS 6 and above, use the above technique, but (a) make sure your
didSelectRowAtIndexPath uses self for the sender and then have
shouldPerformSegueWithIdentifier only allow the segue if the sender
== self (thus when the sender is the table view cell, it will be canceled);
Perhaps even easier, just don't define a segue at all and have your didSelectRowAtIndexPath manually
instantiateViewControllerWithIdentifier and then push/present
that view controller as appropriate; or
You can use the following, kludgy work-around: In short, add a button to your view controller (drag it down to the bar at the
bottom), where it won't show up on the view, but you can use its
segues. So first, drag the rounded button to the controller's bar at
the bottom of the scene:
Now you can make a segue from that button to your view controller:
And thus, you end up with the self-referential segue again:
You must give that segue an identifier, so you can invoke it
programmatically via performSegueWithIdentifier, but it works. Not
the most elegant of solutions, but I think it's better than having an
extra scene on your storyboard.
None of these are ideal, but you have lots of options.
well.. in iOs think about it differently, if i were you i would create multiple ViewController, even if they're almost the same, and create a custom cell class, and make this cell take a configuration block.
Now for each ViewController (( or TableViewController )), you reuse your same custom UITableViewCell and just pass it what's slightly different for each case, and moreover you can also create a BaseTableViewController that will have the general configuration, and in each ViewController pass the custom changes you need.
Think about it, when you look at your storyboard you'll be able to see all your workflow, and when something goes wrong you'll be able to debug, if you have are gonna use what you suggested, debugging will be a real pain i imagine.
Anyway, try it out, and ask more if you need further clarification.

Linking child view controllers to a parent view controller within storyboard

Can you associate child view controllers to a custom container view controller in Storyboard?
I can link child view controllers to a tab view controller, and I can link one view controller to a navigation controller.
What must I do to the container VC to accept child VCs?
As something of a combo of Caleb and Matt's answers, I did:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"cpdc_check_embed"]) {
self.checkVC = segue.destinationViewController;
}
}
...where checkVC is a property on the container controller:
#property (weak,nonatomic) PXPCheckViewController * checkVC;
You just have to set your embed segue's Storyboard ID to whatever you want (in this case, cpdc_check_embed):
...and then check the identifier in -prepareForSegue:sender:.
Still not an outlet, but cleaner than Matt's (IMHO) and more specific than Caleb's, and you still get a nice-looking storyboard:
The storyboard deals with built-in container view controllers very nicely, displaying segues to child/root view controllers so that relationships are clearly shown. It is also nice how the children and parent view controllers are separated into different scenes.
If you want to achieve this effect in your own project, then there is a trick that is not perfect but very straightforward. In my example, suppose I have a container view controller that acts like a tab bar controller with only two tabs, 'left' and 'right'. I want to have a scene represent the parent view controller, and two separate scenes represent both the 'left' child view controller and the 'right' child view controller.
Even though it is impossible, it would be nice if I could create IBOutlets from the container view controller to its children in different scenes, and then when my container view controller is displayed set up the parent/child relationships according to the rules described the UIViewController documentation. If we had references to our 'left' and 'right' child view controllers, then we could set up the relationships no problem.
The standard solution to this referencing problem is to create references to child view controllers by dragging in Object outlets into the container view controller's scene, and then specifying their class type as being instances of the child view controller classes.
In order to keep children separated in different scenes like Apple's built-in containers, however, we will use a different trick. First, suppose we have the following properties declared in our container class, ContainerViewController:
#property (nonatomic, strong, readwrite) UIViewController *leftViewController;
#property (nonatomic, strong, readwrite) UIViewController *rightViewController;
In our storyboard, select the scene representing the 'left' view controller. In the attributes inspector, set the view controller's identifier property to "cvc_leftViewController" ("cvc_" refers to ContainerViewController, but really the identifier can be anything you want). Do the same for the right view controller's scene, setting it's identifier to "cvc_rightViewController".
Now insert the following code into ContainerViewController's viewDidLoad method:
if (self.storyboard) {
_leftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"cvc_leftViewController"];
_rightViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"cvc_rightViewController"];
}
When ContainerViewController is loaded from the storyboard, it will go grab the 'left' and 'right' view controllers from their respective scenes and set references to them via its properties. Now that you have control of the child view controller instances, you can set up the parent/child relationships however you like. To learn how to do that properly refer to the UIViewController documentation.
This trick is not perfect, and has many caveats, but if you are careful you can make it work nicely for your project.
Edit: Although this is completely unnecessary and doesn't mean anything, if you really really want to have the storyboard display connections from your container to your child view controllers just like Apple's built-in containers, just use my method above and then set up segues directly between the container scene to the child scenes, and simply never perform those segues. Now everything will work correctly and look pretty too.
Can you associate child view controllers to a custom container view
controller in Storyboard?
I think what you're asking here is how to connect a view controller in one scene to an outlet of a view controller in a different scene. I don't believe that's possible, perhaps because the storyboard machinery may not have all the scenes in a storyboard loaded at the same time.
You're probably asking this because you want to pass some information from one view controller to another as you segue from one scene to the next. The way to do this when you're working with storyboards is to override -prepareForSegue:sender: in one or both view controllers affected by the segue. The UIStoryboardSegue object provided in the segue parameter has sourceViewController and destinationViewController properties, and also an identifier property. You can use these properties to identify the segue that's about to transfer data between the view controllers.
Ray Wenderlich's blog has a nice two-part tutorial on using storyboards that may help you:
Part 1 covers setting up a storyboard project, adding scenes, and creating segues.
Part 2 deals with using segues to transition between scenes, including the prepareForSeque method mentioned above.
iOS 5 allows multiple view controllers to be active in the same scene (although one should still be in charge), so a single scene in your storyboard might have several controllers. You can use outlets to connect these controllers to each other, and you can configure those connections the same way you did in IB: control-drag from one controller to another in the same scene. The usual outlet list will pop open to let you choose which outlet to connect.
The key to using multiple controllers in one scene (what I believe you are after here) is using the mysterious Object from the Objects list in IB to represent the other view controller and hooking up its outlets.
This answer How to create custom view controller container using storyboard in iOS 5 should help I hope. The answer also provides a working example app which is very helpful.
The problem with #Ben's (otherwise reasonable) answer is that it only works at one level of nesting. Beyond that, it would required that every subsequent VC is customized to save the nesting view controller in prepareForSegue.
To solve this, I spent too much time exploring an NSObject based index that that you could add to the Storyboard, bind to a scene, and which would then register it's parent VC in a global index, based on type and restorationId. That works / can work, but is too much effort in the end, and still requires the two step process of visually binding, and programmatically looking up.
For me, the simplest and most general solution is to lazily descend the view controller hierarchy
In my simple test project, I added the following lines to viewDidLoad:
self.left.data = [
"Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.",
"De carne lumbering animata corpora quaeritis." ]
where left is defined as:
lazy var left:CollectionViewController = { [unowned self] in
return self.childViewControllerWithId("Left") as! CollectionViewController }()
and childViewControllerWithId is defined as:
extension UIViewController {
func childViewControllerWithId(rid:String) -> UIViewController? {
// check immediate child controllers
for vc in self.childViewControllers as! [UIViewController] {
if vc.restorationIdentifier == rid { return vc }
}
// check nested controllers
for vc in self.childViewControllers as! [UIViewController] {
if let vc = vc.childViewControllerWithId(rid) {
return vc
}
}
assert(false, "check your assumptions")
return nil
}
}
Note that you could do other find variants based on type, if need be. Also note that the above requires that you define the restoration id in the Storyboard file. If you did not have repeated instances of the same view controller, then using type would be easier.
And to state what is hopefully obvious, you don't need to implement prepareForSegue, nor do you have to use the lazy loading, you just have to call find(...).

Resources