I've got a UIView designed in InterfaceBuilder that has a Swift backing class. I'm using awakeAfterUsingCoder() to substitute the instantiated nib like so:
public override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
if self.subviews.count == 1 && self.subviews[0].isKindOfClass(UILabel) {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "CustomView", bundle: bundle).instantiateWithOwner(nil, options: nil)
return nib[0] as! CustomView
}
return self
}
However, now I keep getting EXC_BAD_ACCESS crashes in the AppDelegate. I turned on zombies and was able to get this message:
*** -[ModuleName.CustomView _referenceView]: message sent to deallocated instance 0xed13600
_referenceView must be defined somewhere in the framework, though I didn't see it when I navigated self stopped at a breakpoint. What am I doing wrong?
The view in my storyboard was defined as a UIButton (since my CustomView is a UIButton subclass). I replaced it with a generic empty UIView, I replaced the condition with:
if self.subviews.isEmpty {
and everything worked right.
Related
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 am programmatically creating a split view controller using the following code when a table view cell is touched:
let rootViewController: UIViewController = RootTableViewController()
let navVC: UINavigationController = UINavigationController(rootViewController: rootViewController)
let detailViewController: UIViewController = DetailTableViewController()
let splitVC: UISplitViewController = UISplitViewController()
splitVC.viewControllers = [navVC, detailViewController]
self.present(splitVC, animated: true, completion: nil)
but when I tap the tableViewCell I get the error: 'fatal error: unexpectedly found nil while unwrapping an Optional value' which appears to be linked to a UITextField (and all UI Elements) on the RootTableViewController. The first failure is in the viewDidLoad of RootViewController after executing the above code when a value is passed to a ui element
Where exactly does this happen? Wheres the error originated? I had a similar issue where I tried to access IBOutlets before they were created by the system which caused my app to crash.
Specifically I had a UI update function which was called after setting a property of the ViewController.
I got around this by checking for nil in the update function and since it was called before viewDidLoad was called the first time, I called the update function in viewDidLoad manually to make sure that when it shows for the first time, everything is correctly updated.
UPDATE
I think I have an idea of whats going on. You created a Storyboard, setup your UI and chose your ViewControllers as the classes of the ViewControllers in the Storyboard.
If you now want to use the ViewControllers, you have to instantiate them via the Storyboard rather than manually initializing them (something like this):
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "someViewController")
You also could use Segues instead of instantiating something at all. Build your complete UI using the Storyboard and then use a Segue to present the SplitViewController.
The last method I can think of is, that if you want to instantiate the ViewControllers manually and still make use of the Storyboard or a nib, you have to do some custom initialization in in the init functions of your ViewControllers (this code is from a custom view I have in a separate .xib):
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
UINib(nibName: "DatePickerKeyboard", bundle: nil).instantiate(withOwner: self, options: nil)
addSubview(view)
view.frame = self.bounds
}
I've pulled up an old project trying to reuse some functionality.
I've created a nib with a UIView subclass. In the original, working version I didn't need to set File's owner in IB. However, I'm receiving errors alluding that I now need to do so (Xcode 7): loaded the "TipView" nib but the view outlet was not set.
So I proceeded to hook the File's owner up to the view controller that is responsible for setting up the view. Here's the relevant code from the View Controller:
class TipViewController: UIViewController {
private let kTipViewHeight: CGFloat = 400
private let kTipViewWidth: CGFloat = 300
override func viewDidLoad() {
super.viewDidLoad()
if let tipView = createTipView() {
let center = CGPoint(x: CGRectGetWidth(view.bounds)/2, y: CGRectGetHeight(view.bounds)/2)
tipView.center = center
view.addSubview(tipView)
}
}
func createTipView() -> UIView? {
if let view = UINib(nibName: "TipView", bundle: nil).instantiateWithOwner(nil, options: nil).first as? TipView {
view.frame = CGRect(x: 0, y: 0, width: kTipViewWidth, height: kTipViewHeight)
return view
}
return nil
}
}
extension UIViewController {
func presentTips(tips: [Tip], animated: Bool, completion: (() -> Void)?) {
let controller = TipViewController()
controller.modalTransitionStyle = .CrossDissolve
presentViewController(controller, animated: animated, completion: completion)
}
}
I tried setting instantiateWithOwner to: .instantiateWithOwner(self, options: nil).first (changed nil to self) also to no avail.
This is the relevant presenting view controller code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
presentTips([], animated: true, completion: nil)
}
The UIView subclass doesn't do anything out of the ordinary, so I haven't bothered to include its code.
When I make the changes requested - add TipViewController as the nib's File owner and hook up its view outlet to the nib view - I get a range of error messages depending on which configurations I take ranging from:
this class is not key value coding-compliant for the key view.
Can't add self as subview
Not sure why I was able to use a nib without a file's owner in Xcode 6, but am not able to do so as of Xcode 7 - haven't found any material or release notes related to the change, so I'm a little stumped for now.
Any help is very much appreciated.
So I proceeded to hook the File's owner up to the view controller that is responsible for setting up the view
No, you were right the first time. Your problem is that there was a bug having to do with nib names in iOS 8, it was fixed in iOS 9, and you got caught in the net because the bug, ironically, protected you from stumbling across this issue previously.
Do this:
Leave the File's Owner configured as an NSObject.
Do not hook up any outlets from the File's Owner to the view in the nib; if there is such an outlet, delete it.
Load the nib with a nil owner, just as you are doing.
(Ready for this? This is the key:) Rename the nib. Don't call it TipView.xib. That name was what caused the whole issue you've been trying to work around. Call it, let's say, TipViewNib.xib. Revise your code accordingly, i.e. load the nib with nibName: "TipViewNib".
Clear all caches including the simulator version (as I explain here: How to Empty Caches and Clean All Targets Xcode 4) to get rid of the bad nib (the one with the name TipView.xib).
Your problems will then evaporate like morning mist.
I'm trying to make a popup like I described in my previous question. I actually got an answer, but now I have a new problem. I can make the view appear if I don't make the instantiateWithOwner, but it is not responding to anything (just frozen).
In short, I have set up a 'popup.xib' file, which is just a view with a button and a label. My code below should make it appear and disappear with button clicks.
I have read the documentation that the instantiateWithOwner does all the magic of connecting the view to it's callback buttons, so it makes sense that nothing happens when it's not in the code. (reference)
Thing is that if I do include it in my code, I get a compiler error 'PopupViewConrtoller' does not have a member named 'instantiateWithOwner'.
I have tried searching the auto-complete list, but found nothing similar.
My code:
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBAction func showPopup(sender: AnyObject) {
// This line makes it appear on the screen, but not respond to anything.
var x = PickerPopupViewConrtoller(nibName: "PickerPopup", bundle: nil)
// This line does not compile.
var x = PickerPopupViewConrtoller(nibName: "PickerPopup", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as? PickerPopupViewConrtoller
x.show(self.view)
}
}
PopupViewController
import UIKit
class PickerPopupViewConrtoller : UIViewController {
func show(tView : UIView) {
tView.addSubview(self.view)
}
#IBAction func done(sender: AnyObject) {
self.view.removeFromSuperview()
}
}
Yes it's correct, instantiateWithOwner is not a UIViewController method, it is a UINib method.
You have to create a UINib object and then cast it to your UIViewController class
ex:
UINib( nibName: nibNamed, bundle: bundle).instantiateWithOwner(nil, options: nil)[0] as? UIViewController
That's why I use the extension I wrote in the previous answer, it's easier and more readable.
instantiateWithOwner is a method on UINib. You are calling it on a UIViewController instance.
The correct code would be along the following lines:
UINib(nibName: 'PickerPopup', bundle: UIBundle.mainBundle().instantiateWithOwner(nil, options: nil)[0] as? PickerPopupViewController
The instantiateWithOwner method will actually call the PickerPopupViewController constructor (PickerPopupViewController.init(coder:))