I am struggling to figure out how to load a xib from within a storyboard using Swift in XCode.
My main storyboard (Called TabBarNav.storyboard) is a Tab Bar View controller with 4 items (Home, Weight, Meals and Calories).
I have created a seperate XIB UIView called ViewWelcome.xib with corresponding class file ViewWelcome.swift.
The Home items view controller has a class file called "ViewControllerHome.swift"
When the Home tab bar item is touched I want to replace the existing view with the one within ViewWelcome.xib.
In ViewWelcome.xib I have made the files owner ViewControllerHome but when I run the app it is still showing the view that was created with the Home item when I originally created the storyboard.
Reason I am doing it this way: (A Mix of storyboard and xibs) I wanted each section (Home, Weight, Meals and Calories) to be seperate from the storyboard so that the SB doesnt become cluttered and to also later avoid merge issues in git when more than one person works on interface files.
Simplest way is to create & add your nib view in viewDidLoad
func viewDidLoad() {
super.viewDidLoad()
// create and add view from nib
if let viewWelcome = UINib(nibName: " ViewWelcome", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as? ViewWelcome {
viewWelcome.frame.size = self.view.size
self.view.addSubview(viewWelcome)
}
}
Otherwise refer to this
This is working for me . Give a try . .
let newView = Bundle.main.loadNibNamed("ViewWelcome", owner: self, options: nil)?.first as! ViewWelcome
self.view = newView
Related
I'm trying to implement a page menu in my app using the pod from this pre-built page menu pod from github
In the instructions, it says:
var controller : UIViewController = UIViewController(nibName:"controllerNibName", bundle: nil)
controller.title = "SAMPLE TITLE"
controllerArray.append(controller)
things are all set up but i need only "controllerNibName". I am not using storyboard. And haven't made any xib file yet with viewController. so how can i get the NibName of viewController.
controllerNibName string is a file name of your XIB. Where you have done all the UI which is related with your UIViewController.
If your viewController class name and xib file names are same then you can directly allocate the UIViewController. It will pick the xib automatically.
Assume UIViewController name is PageOne & xib name also should be PageOne.xib
let pageOne = PageOne()
if your xib name is different from your view controller name
For Example : Page1.xib
let pageOne = PageOne(nibName:"Page1", bundle: nil)
There is a nice way to get a name of nib (if nib name is equal to controller name i.e. MyViewController.xib and MyViewController):
let controllerName = String(describing: MyViewController.self)
var controller : MyViewController = MyViewController(nibName:controllerName, bundle: nil)
To link a xib file and the custom view one, do we need to invoke the addSubview() method?
We can link them like this:
let subView = NSBundle().loadNibNamed("customVIew", owner: nil, options: nil)[0] as! UIView
self.view.addSubview(subView)
Otherwise, as a static method, we can the same thing by this:
let nib = UINib(nibName: self.className, bundle: nil)
let objects = nib.instantiate(withOwner: ownerOrNil, options: nil)
return objects.first as Self
Do they have any difference? One uses addSubview() and the another doesn't use the method.
One uses addSubview() and the another doesn't use the method
And the second one, which never says addSubview(), never gets the view into the interface. But in the second case, what was the point of saying return objects.first? Very likely so that the caller could say addSubview(). Sooner or later, someone will probably say it, or the loading of the view will have been in vain. Why would you instantiate a view (which is what loading a nib does) if you didn't want to put that view into your interface?
I am trying to display one view or another view inside the detail view of a master/detail based on a conditional.
These views will contain outlets and elements, so I would like to have view controllers for each that I can play with.
So I created a new UIViewController called AddPhotoViewController. This is how I add AddPhotoViewController.xib inside DetailViewController:
let photoVC = AddPhotoViewController(nibName: "AddPhotoViewController", bundle: nil)
let photoView = photoVC.view
photoVC.delegate = self
photoView.autoresizingMask = UIViewAutoresizing.FlexibleWidth
photoView.frame = area.bounds
area.addSubview(photoView)
The view loads properly in the detail view and looks like this:
AddPhotoViewController.xib's owner class has been set as well here:
When I tap the button, though the action is set properly in AddPhotoViewController to print a message, Xcode crashes.
Am I doing this correctly? Is there a more common practice for loading view X or view Y inside a view controller depending on user data?
Button action:
#IBAction func ButtonPressed(sender: AnyObject) {
println("worked!")
}
Button connection:
Console output:
I think you need to add the viewController:
addChildViewController(PhotoVC)
//and then
PhotoVC.didMoveToParentViewController(self)
My list consists of card views with an image and some text fields, all of which have a complex layout. This card is to be reused in the detail view, so containing it inside a UITableViewCell layout and then calling UITableView.registerNib() doesn't seem to work for me. I'd like to know the cleanest way to reuse the view I created in the NIB-file as both a list item and a part of my detail screen, while keeping as much of the view logic such as constraints and style in the NIB-file.
I'd like to know the cleanest way to reuse the view I created in the NIB-file as both a list item and a part of my detail screen
Do exactly what you're already doing: Use a nib (a .xib file) containing just one view, the UITableViewCell as designed, and register that nib with the table view - exactly the thing you say "doesn't seem to work for you". It does work. So now all your table rows consist of copies of this table view cell.
But there's the trick. When you want to show the view elsewhere, load the nib manually, pull out the view (the UITableViewCell), pull out its contentView, and add it as a subview to your interface.
// substitute correct name of xib file here
let arr = UINib(nibName: "MyTableViewCell", bundle: nil).instantiateWithOwner(
nil, options: nil)
let tvc = arr[0] as! UITableViewCell
let v = tvc.contentView // now it's a normal view! customize as needed
v.frame.origin = CGPointMake(100,100) // or whatever
self.view.addSubview(v) // and plop it into the interface! voilĂ !
I have 1 tab bar controller in storyboard and 1 UIViewController associated with it. I would like to re-use the same UIViewController in order to create second item in tab bar. When I am creating second relation from tab bar to view controller I need to specify 2 different items names. How can I re-use same view controller and set different items names from storyboard? If not possible to do it in storyboard, then do I have to rename each in tab bar controller class or there is better way?
I was going to provide different data to view controller in prepareforsegue.
UPDATE:
little more details and clarification
In above screenshot marked VC at the moment is reachable a) directly from tab, b) through 3 transitions. I want to add another DIRECT relation to initial tab bar, just like in case of "a".
I can give you a little tweak for that and at least that worked for me.
Drag a tabbarcontroller and associated tab item view controllers to
your storyboard. Name them as you like.
Create an extra view controller that you want to reuse from your storyboard.
Add container views to each tab item view controllers and remove their default embedded view controllers.
Create embed segue from each tab item controller to your re-usuable view controller.
The configuration looks something like the following:
Thus you can use the same embedded VC for different tabbar item. Obviously if you need the reference of the tabbarcontroller, you need to use self.parentViewController.tabBarController instead of self.tabBarController directly. But it solves the issue of reusing a VC right from the storyboard.
I've found much simpler solution using storyboard only.
Setup your storyboard like this:
Then in your Navigation Controller Identity Inspector set Restoration ID like this:
And in your ViewController class file put the following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = parent?.restorationIdentifier
label.text = parent?.restorationIdentifier
}
or do what you like based on parent?.restorationIdentifier value
If you don't want the Navigation TopBar to appear on the ViewController just set it to None in Attributes Inspector of the desired Navigation Controller like this:
That's it! Hope it helps.
Yes you can.
All you need to do is to create a new View Controller in StoryBoard as if there is going to be a different View Controller for tab 2. Then Select the 2nd view controller and simply add its class name the same classname of view controller 1
Things to note:
When you are sharing the same view controller class (.m ad .h) files, each tab will create a NEW instance of that class.
Edit:
This works as long as you have either a "custom" cell scenario (i.e. reusing two table view controllers) OR, have all your views inside a "container view" (i.e. reusing UIView).
I needed slightly different solution than the accepted answer. I needed to use same Table View Controller with the different data source for different tab bar items. So in the storyboard, i created two Navigation Controllers with same classes like this;
I also give different "Restoration ID" to each of them.
For the first one, I gave "navCont1" and "navCont2" for the second one.
In subclass("GeneralNavCont") of these Navigation Controllers; I override init method and check restoration id of self. Then i initiate my TableViewController and set its data source based on ids like this;
class GeneralNavCont: UINavigationController {
var dataSource1 = [Countries]()
var dataSource2 = [Cities]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initiateTableVCBasedOnId()
}
func initiateTableVCBasedOnId() {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let tableVC = storyBoard.instantiateViewController(withIdentifier: "tableVC") as! MyTableViewController
if self.restorationIdentifier == "navCont1" {
tableVC.dataSource = self.dataSource1
self.viewControllers = [tableVC]
}
else if self.restorationIdentifier == "navCont2" {
tableVC.dataSource = self.dataSource2
self.viewControllers = [tableVC]
}
}
}
Hope it helps someone. Cheers.