Ive been searching for a while for a simple example on how to reuse views from xib files in my storyboard but all i find was outdated or dosen't solve my problem
the situation is that I has simple:
I have a viewController in my storyboard
I dragged to it two view from the library
I had created a myCustomView.xib and myCustomView.swift file (they are empty now)
I have I button on viewController (so the tree (two view and one button) are setting together on the viewController in the storyboard)
the question is: I want one view to be loaded dynamically on app launch and the other one to be loaded on button click
an other question: how can I connect myCustomView to that viewController
thank you
I've implemented an extension for UIView:
extension UIView {
static func createInstance<T: UIView>(ofType type: T.Type) -> T {
let className = NSStringFromClass(type).components(separatedBy: ".").last
return Bundle.main.loadNibNamed(className!, owner: self, options: nil)![0] as! T
}
}
In this way, wherever you can load your custom view in this way:
func override viewDidLoad() {
super.viewDidLoad()
let customView = UIView.createInstance(ofType: CustomView.self)
self.view.addSubview(customView)
}
Add bellow code in your custom view class
class MyCustomView: UIView {
#IBOutlet var contentView: UIView! // take view outlet
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
contentView = loadViewFromNib()
// use bounds not frame or it'll be offset
contentView!.frame = bounds
//Make the view stretch with containing view
contentView!.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(contentView!)
layoutIfNeeded()
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
print("layoutIfNeeded")
}
func loadViewFromNib() -> UIView! {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
Add this class as superclass view in storyboard.
Related
Assume I have a custom UIView class ColorWheelView.swift and XIB ColorWheelView.xib.
To create an custom UIView from XIB via code, here's the common practice
Via Code
extension UIView {
static func instanceFromNib() -> Self {
return getUINib().instantiate(withOwner: self, options: nil)[0] as! Self
}
static func getUINib() -> UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
}
// Create ColorWheelView from XIB.
let colorWheelView = ColorWheelView.instanceFromNib()
Via Storyboard (Doesn't look like a right way)
But, how about Storyboard? What if I use ColorWheelView as a subview in Storyboard? How can I inform Storyboard that ColorWheelView should be constructed directly from ColorWheelView.xib?
A common way I have seen so far is discussed in https://stackoverflow.com/a/34524346/72437 and https://stackoverflow.com/a/34524583/72437
import UIKit
class ColorWheelView: UIView {
let nibName = "ColorWheelView"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
But, such code just doesn't look right to me. It merely
Storyboard creates a "parent" ColorWheelView without using XIB.
Then, "parent" ColorWheelView creates another "child" ColorWheelView from XIB, and used it as subview of itself.
Doesn't seem like an optimised way, as now we are having 2 instances of ColorWheelView.
Is there a better way, to tell Storyboard that I want to create a custom subview from an XIB?
import UIKit
#IBDesignable class ColorWheelView: UIView {
let nibName = "ColorWheelView"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
By putting designable keyword before class name will make this view to be used in storyboard way you want(by putting custom view class name in identity inspector.
I trying to using my custom xib files. But those UI are not showing in main view controller in xcode. But it show when run the app. I already added #IBDesignable in class. Here is what i did.
I created TestView.xib and added some design
I created TestView.swift
I updated fileOwner of TestView.xib with TestView in Custom Class
I added init code to testView.swift
Code inside TestView.swift
import UIKit
#IBDesignable class DetailView: UIView {
override init(frame: CGRect){
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
}
func setup() {
let view = Bundle.main.loadNibNamed("TestView", owner: self, options: nil)?.first as! UIView
view.frame = self.bounds
self.addSubview(view)
}
}
And Added on UIView in MainViewController and link to TestView with custom class name.
After that i run the app, I can see the UI from testView. Image of emulator and result
But my custom UI don't show in MainViewController in xcode Image of xcode view controller
How can i enable to see my custom UI in MainViewController ? Thanks
In InterfaceBuilder select your ViewController and choose "Editor-->Refresh all Views". Then your custom view should come up.
If something is wrong you can debug your IBDesignable class by setting breakpoints in your code and then choose "Editor-->Debug selected views".
prepareForInterfaceBuilder() only needs to be implemented if you need special design appearance in InterfaceBuilder.
Sometimes it is necessary to clean the project and build folder to get it to work:
Project->Clean
Project->(hold down options key)->Clean Build Folder
I just changed the setup function. It working now. Final Result Image - It showing UI in IB
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue)))
self.addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "TestView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
Override two func below for live:
override open func prepareForInterfaceBuilder() {
layer.cornerRadius = cornerRadius
layer.borderWidth = borderWidth
}
override open func awakeFromNib() {
layer.cornerRadius = cornerRadius
layer.borderWidth = borderWidth
}
What I've done is:
1) Created a .xib file TranslationInfoWindow.xib:
2) Created TranslationInfoWindow.swift file with the follow content:
import UIKit
class TranslationInfoWindow: UIView {
// MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
// MARK: - Private Helper Methods
// Performs the initial setup.
private func setupView() {
let view = viewFromNibForClass()
view.frame = bounds
// Auto-layout stuff.
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
// Show the view.
addSubview(view)
}
// Loads a XIB file into a view and returns this view.
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
#IBOutlet weak var avatarImageView: RoundedImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var usersLabel: UILabel!
}
3) Here I try to initialise my custom view:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
// FIXME: There is a UIView but it doesn't want to be casted in TranslationInfoWindow
if let infoWindow = Bundle.main.loadNibNamed(
"TranslationInfoWindow", owner: view, options: nil)?.first as? TranslationInfoWindow {
return infoWindow
} else {
return nil
}
}
Now if I try to run the project I have the following error:
What am I doing wrong?
UPDATE:
Here's the hierarchy of xib:
In Interface Builder, did you change the class name in the Identity Inspector (3rd from left) tab from UIView to your custom class name?
You should set the correct class of TranslationInfoWindow.xib to be type TranslationInfoWindow in IB .
I made a subview appear on my iOS app when a button is pressed. Then, I call a custom UIView from its class, menuView, and display that UIView on my main storyboard
The menuView appears fine on my Main Storyboard, but once I remove the subview, my SuperView (original view for main class for main view controller) is unresponsive, and I can't interact with anything.
What's wrong?
My Custom "menuView" UIView Class:
import UIKit
#IBDesignable class menuView: UIView {
var view:UIView!
#IBOutlet weak var label: UILabel!
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "menuView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
xibSetup()
}
}
Main View Controller Class:
var myMenuView:menuView!
#IBOutlet weak var menuButtonOutlet: UIBarButtonItem!
#IBAction func buttonPressed(sender: AnyObject) {
if menuButtonOutlet.title == "Menu" {
if (myMenuView != nil) {
self.myMenuView.view.removeFromSuperview()
}
self.myMenuView = menuView(frame: self.view.bounds)
self.myMenuView.alpha = 0.75
self.view.addSubview(myMenuView)
menuButtonOutlet.title = "Back"
} else {
self.myMenuView.view.removeFromSuperview()
menuButtonOutlet.title = "Menu"
}
}
Short answer:
self.myMenuView.view.removeFromSuperview()
should become
self.myMenuView.removeFromSuperview()
in both places you have this line in buttonPressed.
Explanation:
Your myMenuView is a container in which you place the view you instantiate from your xib. In buttonPressed you remove only this inner(xib) view inside myMenuView and not myMenuView itself. Thus myMenuView remains on screen and swallows all the touches.
The task is to create custom UIView that loads UI from .xib file using Swift. How do I do that?
I tried to do that using the code:
import UIKit
#IBDesignable class CustomView: UIView {
var view: UIView!
#IBOutlet weak var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "CustomView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
It runs, but crashes with EXC_BAD_ACCESS and shows me the message:
warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available.
Actually, I need to translate code given here http://qnoid.com/2013/03/20/How-to-implement-a-reusable-UIView.html to Swift 2.0, but I have no idea how to do this.
Try
let yourView = NSBundle.mainBundle().loadNibNamed("youViewNibName", owner: self, options: nil).first as! YourView
Do it in your ViewController class, then you can access the view in required init with coder, where you should call xibSetup().
Is it correct, that your class is called CustomView and you're loading CustomView.xib where the class for the view in xib is CustomView?
If that is so you probably do not understand what you're doing