I usually use autolayout together with size classes, to optimize the UI for larger screens, like on iPad.
However, sometimes I need a completely different layout for the UI on iPad, still containing the same view controllers, but structured differently.
What is the best strategies in such situation, where autolayout and size classes are not enough? Loading a different storyboard for iPad?
Obviously the goal here is to not introduce any code duplication.
thanks
Loading a different storyboard for iPad? Obviously the goal here is to not introduce any code duplication.
If that's what you want to do, that feature is built-in and expected. You don't need any code at all. Just create your two storyboards and edit the Info.plist so that it has two main storyboard entries that point to them:
The right thing will just happen: on iPad, the second storyboard will load at launch.
I do same thing with size classes on UIViews and NSLayoutConstraints, for an example if i have different views for pad and phone, but i must do same logic for sections, i create two different views in same storyboard and enable/disable views based on size classes
Make Use of 2 Views(xib) with a single class file. The number of connections has to be same in both xibs but the placement of elements can be different for iPad and iPhone.
Here Below is my code to load XIB from a viewcontroller.
class ViewController: UIViewController {
var customView:CustomView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
loadCustomView()
}
func loadCustomView(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let window = appDelegate.window {
customView = CustomView(frame: CGRect(x: 0, y: 0, width: window.frame.width, height: window.frame.height))
window.addSubview(customView!)
}
}
}
Then there is the UIView Class
import UIKit
class CustomView: UIView {
#IBOutlet weak var lbl_Title: UILabel!
var view:UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = UIView.AutoresizingMask.flexibleWidth
view.autoresizingMask = UIView.AutoresizingMask.flexibleHeight
addSubview(view)
}
func loadViewFromNib() -> UIView {
if UIDevice.current.userInterfaceIdiom == .phone{
let bundle = Bundle(for:type(of: self))
let nib = UINib(nibName: "CustomView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}else{
let bundle = Bundle(for:type(of: self))
let nib = UINib(nibName: "CustomViewTab", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
}
You make the outlet connections to both the XIBs from this class and load the views depending on device type.
Related
i did make some custom views to be able to reuse them in different controller .xib files.
I do initialize them with:
var view: UIView!
var nibName = "MyCustomView"
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
self.backgroundColor = Theme.colorTransparent
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: self.nibName, bundle: bundle)
self.view = nib.instantiate(withOwner: self, options: nil).first as! UIView
self.view.frame = self.bounds
self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(self.view)
clearErrors()
}
But everytime i open a controller .xib file which contains at least one custom view, XCode starts recompiling.
Sometimes also the position and size of all views (not only custom) are distorted. I then have to wait for the recompile to finish, before i can click the yellow triangles to fix the misplacements.
This behaviour is very annoying...
Can you help me to avoid the misplacements and the recompiling so that everything runs smooth and fast?
BTW: the custom views do work properly, it's just the waiting for the recompiling and the misplacements, which i was not able to fix
Thank you very much
Try: Editor > Automatically Refresh Views > Uncheck.
This should stop the constant rebuilding, but yes, it appears like just another annoying Xcode bug.
After updating to Xcode 8.1 our storyboards and xib are as usual. If we create a new xib/view/storyboard we can't see the freshly added elements in the new view.
Both of these are xib files. The left one is created befor the update, the right one after. You cant see the button eventhough that it is on top of everything and has contrains to fill the view. It also has text content and no sized classes.
I know that there are alot questions which explain that this could be sized classes.
why storyboard ui elements not showing on UIViewController in xcode 6?
Storyboard UI Elements not displaying in editor
If we add new elements to it they are directly not visible. Also adding new ViewController to a "old" storyboard it does not show its content if we add elements to it.
What is going on here and how do i solve that?
For example, we have TopView (my CustomView):
TopView.xib, set TopView class in File's Owner
TopView.swift
import UIKit
#IBDesignable
class TopView: UIView {
//MARK:- IB Outlets
var contentView:UIView?
//MARK:- Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupThisView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupThisView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupThisView()
contentView?.prepareForInterfaceBuilder()
}
//MARK:- Lifecycle methods
private func setupThisView(){
guard let view = loadViewFromNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let nibName = String(describing: TopView.self)
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self,options: nil).first as? UIView
}
}
3.Add UIView in Storyboard and set the class of the view as TopView
If the TopView has not yet appeared in Storyboard then (this happens usually):
Enable Editor -> Automatically Refresh Views
Click on Editor -> Refresh All Views
Still?
Clean the project: ⌘ + K
Build the project ⌘ + B
Result in Storyboard:
p.s I just copy this one from my answer here: Custom view (xib) not visible on storyboard
1. Open the Xib/Storyboard
2. Select the file attributes tab (on the far left)
3. click the "opens in" property and change it from Xcode 8 to Xcode 7
I have a scenario where in I need to create around 10 different prototypes of UITableViewCells. How ever all of these cells have some UI Elements (area marked in black) in common. And there is an area (marked in yellow) which is different for all these prototypes.
Is there a way I can abstract all the common UI Elements like the way it is done for contentView in UITableViewCell?
I tried to create a TableViewCell with all these elements and empty UIView to hold the customizations and planned to programatically load UIView (created in separate xib) into it.
The problem is I am not able to load the UIView into the UITableViewCell without loosing the constraints?
How to load a custom view with constraints into another?
Or Is there a way to create a custom UITableViewCell like the one in IB?
Create LoadableFromXibView subclass
Create a xib file, set the File's owner as your subclass
Drag outlets and design your view as usual
In your cell insert a subview that will be your reusable part of the cell and set its name as the new LoadableFromXibView subclass that you have created.
Source here: https://gist.github.com/DenHeadless/c3d682e7f499113109d6
class LoadableFromXibView: UIView {
var view = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromXib()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
backgroundColor = .clearColor()
addSubview(view)
}
private func loadViewFromXib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil).first as! UIView
return view
}
}
I have created a custom UIView in MySample.xib. I have added the class MyView to the File Owner of xib.
MyView.swift
class MyView: UIView {
#IBOutlet var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
NSBundle.mainBundle().loadNibNamed("MySample", owner: self, options: nil)
self.addSubview(self.view)
}
}
I am now loading this MyView from MyController file like this:
MyController.swift
class MyController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
view.addSubview(MyView())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now to display this view, I am using to following code from another controller's UIButton:
presentViewController(MyController(), animated: true, completion: nil)
This does display the view on screen. But the problem is, it doesn't accept any user interaction. In my custom view, I have a UITableView which does display the data but it doesn't scroll or get tapped due to lack of user interaction.
Any idea what I am doing wrong?
There are some unnecessary things in your example.
I am still not sure what are you trying to do, but if you want to add a custom view from xib to your view controller then:
Create a view in a xib file , you don't need to override init , and you can't init view from xib using the default init UIView() , so please remove init method from your MyView class.
In your xib make sure that your view that you see in the IB is of the class type you want to use (i guess MyView class).
In your view controller init the view like this:
class MyController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Get all views in the xib
let allViewsInXibArray = NSBundle.mainBundle().loadNibNamed("MySample", owner: self, options: nil)
//If you only have one view in the xib and you set it's class to MyView class
let myView = allViewsInXibArray.first as! MyView
//Set wanted position and size (frame)
myView.frame = self.view.bounds
//Add the view
self.view.addSubview(myView)
//TODO: set wanted constraints.
}
}
You don't have to re-instantiate this twice
already if you using the design pattern.
It's so simple. Just write:
class MyController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Get all views in the xib
let view = MyView()
self.view.addSubview(myView)
//TODO: set wanted constraints.
}}
And It will work.
Instead of linking xib File's Owner class to MyView, I have to change the class of root view in xib to MyView. Then based on #Oleg Sherman code, it works perfectly with small changes of adding MyView() as owner to get all it's events, otherwise it will throw an error this class is not key value coding-compliant for the key ****.:
let allViewsInXibArray = NSBundle.mainBundle().loadNibNamed("MySample", owner: MyView(), options: nil)
Using File's Owner class to MyView is only required when you have to use the xib in Storyboard.
Not sure if there is a workaround to use File's Owner class to MyView when programmatically loading xib from custom controller like in my original question.
I am trying to apply background Image to all my screens in my iOs application.
My initial screen is a Registration screen where i want to apply the background image, the registration screen is as shown below:
I am using a custom UIView from xib file to apply the background image.
But after applying the background image it only shows the background image on the Registration screen and removes the login controls. I dont know why.
Following is the registration screen after applying the image background.
One more thing, I can see the background image only on the run time not in the Storyboard.
Following is my implementation
I have created one .xib file having a simple UIView, this UIView would serve as a master view in all my application. To obtain the UIView from nib file and to apply further settings I am using a custom UIView class. In this custom UIView class I am applying the background image on the UIView obtained from the nib file. As you can see above I am applying this custom UIView class to the Registration screen UIView. Following is the code as shown below:
class BackgroundMap: UIView {
var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
view = loadViewFromNib ()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
view.backgroundColor = UIColor(patternImage: UIImage(named: "MapWantd")!)
self.addSubview(view)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
view = loadViewFromNib ()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
view.backgroundColor = UIColor(patternImage: UIImage(named: "MapWantd")!)
self.addSubview(view)
}
func loadViewFromNib() ->UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "BackgroundMap", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}