I'm trying to use custom views from xib files, but I don't know how to set the placeholder view in the storyboard equal to one of the xibs. Also, I can't just set the placeholder uiview's property to one of the xibs because I need to be able to switch between different xibs. This is what I have so far.
import UIKit
class Viewcontroller: UIViewController {
#IBOutlet weak var placeholderView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
placeholderView = view
}
class func instanceFromNib() -> CustomView {
let view = UINib(nibName: "CustomView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CustomView
return view
}
Create a custom class for your Xib view where you load the nib as follows:
class MyCustomView: UIView{
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
}
}
You can add all of the outlets and stuff you need there.
The view in your storyboard must be of the type of your Xib view. Change it both in your code and in your storyboard:
#IBOutlet weak var placeholderView: MyCustomView!
From storyboard:
Click the view
Go to the identity inspector
In the class thing write MyCustomView
Instead of thinking in terms of a "placeholder" view that will be replaced by some other view, think about it as a "container" view... you would add the xib's view as a subview, just as you would add any other subview.
So, when you want to show the view from a xib, you would:
remove the current subview from the container (if one is already there)
load the view from the desired xib
add it as a subview to the container
set constraints relative to the container
Related
I'm trying to use a custom uiview that is in a xib file. I'm a beginner and don't know where to start. I have this so far, but I don't know what I should set my placeholder view equal to
edit: full viewController:
import UIKit
class Viewcontroller: UIViewController {
#IBOutlet weak var placeholderView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
placeholderView = view
}
class func instanceFromNib() -> CustomView {
let view = UINib(nibName: "CustomView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CustomView
return view
}
A. Let me suppose that you have a xib file titled 'TestView.' Also let me suppose that you have a subclass file of UIView titled 'TestView.'
B. Select your xib file. And select File's Owner. And set the class to TestView (the name of the subclass file).
C. Open the xib file with Interface Builder. Select 'Custom View.' (in the middle pane) IBOutlet-Connect this view to your UIView swift file. For example, name it like the following. #IBOutlet var customView: NSView!
D. This subclass file should have the following.
import UIKit
class TestView: UIView {
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("TestView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
}
}
E. Open the storyboard. Drag and drop a Custom View onto the view controller scene. Under the identity inspect, set the class name to TestView.
F. IBOutlet-connect the custom view object in the view controller like the following.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var testView: TestView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
That's all.
Hello there.
Using Swift 4, I am attempting to load a Custom UIView with XIB onto a UIViewController.
However, it only seems to fill the screen partially, and I'm not sure why.
I did the following:
The view controller is defined in a UIStoryboard
UIViewController that adds the UIView in the viewDidLoad
The UIView swift file and the XIB are connected via the File Owner property
The XIB file is added into the copy bundle resources
The hot pink background color is set using the Xcode visual editor, its not done in code.
I simulate using the iphone xr, but I get the same issue if I simulate on iPhone 6s
The view controller code is empty, but I've included the relevant part:
// QuestionViewController
class QuestionViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let subview = QuestionView()
self.view.addSubview(subview)
}
}
The UIView is also pretty basic:
class QuestionView: UIView, XibView {
override init(frame: CGRect) {
super.init(frame: frame)
setupXib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupXib()
}
func setupXib() {
guard let v = loadFromXib() else {
return
}
addSubview(v)
}
}
I use a protocol that I found on stackoverflow to load the xib file from bundle. Originally I had a lot of issues even loading the bundle, but fortuently I was able to rectify this issue. Anyway, my XIB protocol file is here:
// XIB protocol file
protocol XibView {
func setupXib()
func constrainView(_ view: UIView)
func loadFromXib() -> UIView?
}
extension XibView where Self: UIView {
func setupXib() {
if let xibView = loadFromXib() {
addSubview(xibView)
constrainView(xibView)
}
}
func constrainView(_ view: UIView) {
view.translatesAutoresizingMaskIntoConstraints = false
addConstraints(
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[view]|",
options: [.alignAllCenterX, .alignAllCenterY],
metrics: nil,
views: ["view": view]
)
)
addConstraints(
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[view]|",
options: [.alignAllCenterX, .alignAllCenterY],
metrics: nil,
views: ["view": view]
)
)
}
func loadFromXib() -> UIView? {
let xibView = UINib(nibName: String(describing: Self.self), bundle: Bundle(for: type(of: self))).instantiate(withOwner: self, options: nil).first as? UIView
return xibView
}
}
--
Question:
Why does the UIView not fill the entire screen or only fill the screen partially and how can I resolve this?
With thanks
Edit:
The storyboard looks for the UIViewController only has a single view with no content.
I think you should take a UIview(0,0,0,0 four constraints) in Your Viewcontroller and then assign it a custom class which is a subclass of UIView and then load the Xib file and it will surely occupy the whole screen
Try this man::----
class QuestionViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let subview = QuestionView()
subview.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.width)
self.view.addSubview(subview)
}
}
I create a framework with Custom View. When I'm try to load this to simulator, it loads but when there's a bug when load in deferent iPhone simulators.
I set the constraints to 0, but the custom view doesn't load in all view.
framework code:
#IBDesignable
public class SummaryHeaderView: UIView {
/* View */
#IBOutlet public var view: UIView!
public override init(frame: CGRect) {
super.init(frame: frame)
setUpNib()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpNib()
}
internal func setUpNib() {
view = loadNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
internal func loadNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: SummaryHeaderView.self), bundle: bundle)
guard let summaryHeaderView = nib.instantiate(withOwner: self, options: nil).first as? UIView else {
return UIView()
}
return summaryHeaderView
}
}
app code:
#IBOutlet weak var myView: UIView!
var summary = SummaryHeaderView()
override func viewDidLoad() {
super.viewDidLoad()
summary = SummaryHeaderView(frame: self.myView.bounds)
myView.addSubview(summary)
}
Result:
One serious problem with your code is this:
override func viewDidLoad() {
super.viewDidLoad()
summary = SummaryHeaderView(frame: self.myView.bounds)
}
You are assuming here that self.myView.bounds is known at the time viewDidLoad runs. That assumption is wrong. viewDidLoad is too soon for that. Therefore your summary header view shows at the wrong size.
A simple solution would be: instead of using absolute frames everywhere, use auto layout constraints everywhere. That way, all your views and subviews will adjust themselves automatically no matter how big the screen turns out to be.
Another way to do this is to wait until the size of the view is known. For example, move your code into viewDidLayoutSubviews. But then you must take extra steps so that you don't add the subview again later, because viewDidLayoutSubviews can be called many times during the app's lifetime.
i have a problem is there any way to make something like outlet in my custom UIView class. I connect view with class via
class func instanceFromNib() -> UIView {
return UINib(nibName: "AddressView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
my whole class looks like
class AddressView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
var view = AddressView.instanceFromNib()
self.addSubview(view)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
var view = AddressView.instanceFromNib()
self.addSubview(view)
}
class func instanceFromNib() -> UIView {
return UINib(nibName: "AddressView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
}
You can connect IBOutlets from Storyboard if AddressView is subclass of UIViewController and you make your xib of class AddressView (selecting AddressView as custom class from the Identity Inspector of your xib File's Owner)
I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!