I have a UIViewController with a button that opens a Modal/Popup (XIB)
I use this code for that:
let aView: myView1 = UINib(nibname: "AView", bundle : nil).instantitate(withOwner: self, options: nil)[0] as! myView1
aView.frame = self.view.frame
self.view.addSubView(aView)
The Modal contains a button that should open a Form (another XIB)
I use this code for that:
let bView: myView2 = UINib(nibname: "BView", bundle : nil).instantitate(withOwner: self, options: nil)[0] as! myView2
bView.frame = self.view.frame
self.view.addSubView(bView)
But I get a compile time error, saying:
Value of type 'AView' has no member 'view'
When I changed the code from:
self.view.addSubview(bView)
to:
self.addSubview(bView)
I get a runtime error when using above line in UIView:
this class is not key value coding-compliant for the key view.
If I relocate the above line to UIViewController, I get a different error:
Can't add self as subview
Is there some other way that works to open a UIView from another UIView, without going back or making changes to the UIViewController?
First i suggest change class name from myView1 to MyView1, same to myView2. Then set your nib files file's owner from interface builder to MyView1 and MyView1.
Change this withOwner: self to nil in below call:
let aView:myView1 = UINib(nibname: "AView", bundle : nil).instantitate(withOwner: self, options: nil)[0] as! myView1
OR
You might need to remove file's owner from interface builder.
Related
I have an .xib file with a few different view elements. Now I make several instances of it in my ViewController with this:
UINib(nibName: "\(self)", bundle: nil).instantiate(withOwner: nil, options: nil).first
Now I am not sure, how to access my viewElements inside this UIView
I could do myView.subviews() and iterate over them. But is there any way to get them by their label, so that I can directly change the text of a UITextField from my xib for example?
You've got a few options.
The first one would be to place all the views in your xib that are not related at top level, then you can access them by their index. For example, if you have a UIView, a UITextField and a UILabel, in that order, you can get the text field like this:
let textField = UINib(nibName: "\(self)", bundle: nil).instantiate(withOwner: nil, options: nil)[1] as? UITextField
The second option, if you want to keep your views as subviews, would be to create a container class with IBOutlets for it's views, set it as the class of the root view, and connect the outlets, then you'd get the text field like this
let textField = (UINib(nibName: "\(self)", bundle: nil).instantiate(withOwner: nil, options: nil).first as? ContainerClass)?.textFIeld
Instead of a container class, you could also access them by the index of the subview.
let textField = (UINib(nibName: "\(self)", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView)?.subviews[1] as? TextField
Another option is to use generics, this solution is clever an Swifty but will only work if you only have one view of the given class. If you have to text fields, it will return the first one.
func findFirstElement<T>(of kind: T.Type, in array: [Any]) -> T? {
for element in array {
if let found = element as? T {
return found
}
}
return nil
}
You get the idea. You can use it as it is, as an extension of UINib that goes through it's contents, or as an extension of UIView that finds the subview that matches.
The cool thing is that the return value of that object is already of the type you expect, the downside is that it'll only find the first one.
Personally, I would use the first option. If the views are unrelated, I wouldn't add them as subviews of another.
I have custom Subclass of UIView with an associated .xib file. In the .swift file of my class inside the required init?(_:):
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let xibView = loadViewFromNib(named: "RangeSelectorView")
xibView.frame = self.bounds
xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(xibView)
}
I'm calling loadViewFromNib(_:), which does the following:
func loadViewFromNib(named name: String) -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: name, bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
Inside of a prototype cell of a TableViewController in my Storyboard, I have a UIView, which shall be an instance of my custom class. The code does compile without any problems but as soon as the TableViewController should appear on screen, it crashes without giving me any explicit error message.
Any ideas what i'm doing wrong?
--Update:--
If I comment out the required init?(_:) and the only outlet of my .swift file (the view itself) of my custom view everything works fine so far. But then I have the problem that I can't connect Actions to repeating content, which is the case when being inside the content view of a prototype cell. This was the reason why I'm trying to source out the class to a .xib file because it will have two different gesture recognizers and some Actions triggered by them.
I'm creating a custom presentation controller for dimming the background when a view controller is presented. The presentation controller adds a couple of subviews when the transition begins which works great.
However, I would like to setup the chrome (the presentation "frame") in Interface Builder because that way it's easier to layout. Thus, I created a XIB file for designing the chrome. It includes a semi-transparent background view and a ❌-button in the upper left corner to dismiss the presented view controller. For these subviews I need outlets and actions in my presentation controller (which is not a UIViewController subclass).
In order to achieve that I set the XIB's file's owner to my custom presentation controller, both in Interface Builder and in code when instantiating the view:
lazy var dimmingView = Bundle.main.loadNibNamed("PresentationChromeView",
owner: self,
options: nil)?.first
as! UIView
I then created the respective outlets and actions by CTRL+dragging to my presentation controller:
#IBOutlet var closeButton: UIButton!
#IBAction func closeButtonTapped(_ sender: Any) {
presentingViewController.dismiss(animated: true, completion: nil)
}
However, at run-time the app crashes because UIKit cannot find the outlet keys and when removing the outlets the actions methods are not triggered. So in neither case is the connection established.
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_SwiftValue 0x600000458810> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key closeButton.'
The only reason I can think of why this doesn't work would be that it's not allowed to create outlets and actions with classes that don't inherit either from UIView or UIViewController.
Is that assumption correct?
Is there a way to create outlets and actions with non-view(-controller) classes?
You can create IBOutlets in any class inheriting from NSObject. The issue here seems to be that you didn't set your custom class in interface builder:
'[<NSObject 0x60800001a940> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key closeButton.'
While decoding your Nib, NSCoder attempts to set the closeButton property on an instance of NSObject, which of course doesn't have this property. You either didn't specify your custom class or made an invalid connection.
OK... the main problem is that the XIB / NIB file has to be instantiated, not just load the first item.
All these changes are in DimmingPresentationController.swift:
// don't load it here...
//lazy var dimmingView = Bundle.main.loadNibNamed("DimmedPresentationView", owner: self, options: nil)?.first as! UIView
var dimmingView: UIView!
then...
private func addAndSetupSubviews() {
guard let presentedView = presentedView else {
return
}
// Create a UINib object for a nib (in the main bundle)
let nib = UINib(nibName: "DimmedPresentationView", bundle: nil)
// Instante the objects in the UINib
let topLevelObjects = nib.instantiate(withOwner: self, options: nil)
// Use the top level objects directly...
guard let v = topLevelObjects[0] as? UIView else {
return
}
dimmingView = v
// add the dimming view - to the presentedView - below any of the presentedView's subviews
presentedView.insertSubview(dimmingView, at: 0)
dimmingView.alpha = 0
dimmingView.frame = presentingViewController.view.bounds
}
That should do it. I can add a branch to your GitHub repo if it doesn't work for you (pretty sure I didn't make any other changes).
The issue that caused the app to crash was that the dimmingView's type could not be inferred (which is strange because the compiler doesn't complain). Adding the explicit type annotation to the property's declaration fixed the crash. So I simply replaced this line
lazy var dimmingView = Bundle.main.loadNibNamed("PresentationChromeView",
owner: self,
options: nil)?.first
as! UIView
with that line:
lazy var dimmingView: UIView = Bundle.main.loadNibNamed("PresentationChromeView",
owner: self,
options: nil)?.first
as! UIView
The outlets and actions are now properly connected.
Why this type inference didn't work or why exactly this fixes the issue is still a mystery to me and I'm still open for explanations. 🙂❓
I'm defining a Custom UIView based on a XIB. Therefor I have copied CustomXIBSwift-master from Github. That works very well. I adapted it briefly for my purposes.
The label (lblTitle) is linked as a referencing outlet to the File's Owner
Now I also want to make a referencing outlet for the second sublabel to File's Owner. However I'm not able to linke this using the dot-line method nor by including the IBOutlet manually.
Based on the CustomXIBSwift-master the XIB is of type UIView. When I specify a custom class (instead of UIView) it crashes.
Part of the SWIFT code:
func loadViewFromNib() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "SlidingAlertView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view);
}
How can I make a referencing outlet from this View to the Swift file?
I was not able to make a connection between the UILabels and the File's Owner. It appeared that also for the File's Owner thee Custom Class can be specified.
I want to load a UIView from a XIB file, and I was able to do so successful via:
let nib = UINib(nibName: "CardView", bundle: nil).instantiateWithOwner(nil, options: nil).first as! UIView
Where CardView is the name of my XIB file. Now when I try to add an IBOutlet to the XIB file:
I get this error:
'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key phraseLabel.
I have checked that there aren't any orphan IBOutlets in the XIB file. So thinking that it was because I didn't cast the nib to a CardView class I tried this:
let nib = UINib(nibName: "CardView", bundle: nil).instantiateWithOwner(nil, options: nil).first as! CardView
But I end up with this error:
Could not cast value of type 'UIView' (0x10c058df8) to 'LazyFlashCards.CardView' (0x10934bb00).
So I'm guessing that I'm instantiating the XIB file wrongly but I don't know the "correct" way to do it
If you're having issues with this in tests you need to set the owner to your test target.
bundle.loadNibNamed("myNib", owner: testSubject) // owner != nil
I just wrote example to load uiview from XIB by swift, you can read, hope this help you!
https://github.com/lengocgiang/loadViewFromXib