Connect programmatically created view to IB - swift

Following this answer, I managed to add half-modally presented ViewControllerB to ViewControllerA. However, in the example, ViewControllerA is just a red view. I would like to connect it to IB in order to customize it. I tried creating a view controller in IB of class ViewControllerB and connect one of its views to #IBOutlet var menuView: UIView! instead of creating menuView programmatically (as in the example). However, nil was found when adding menuView to the view as a subview. Any help would be great. Thanks!

A couple of observations:
In that example, there is a line that says:
let vc = ViewControllerB()
You could define this view controller’s view in a NIB and this would work fine. But if you’re using storyboards, replace that with ...
let vc = storyboard!.instantiateViewController(withIdentifier: “identifier")
... where identifier is some string that you supplied as the “Storyboard ID” in the “Identity inspector” in IB for that scene.
If this is being triggered by a button or something like that, you can also remove this gotoVCB in that sample, and just do the modal presentation right in IB like usual (e.g. control-dragging from the button to the next scene) and choosing “Present modally”. But you’d have to make sure that you configured all init methods, e.g.:
class ViewControllerB: UIViewController {
// used if you use NIBs or just call `init()`
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
configure()
}
// used if you use storyboards
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
modalPresentationStyle = .custom
transitioningDelegate = self
}
...
}
Unrelated to the immediate question, I might suggest splitting up that answer’s code into separate objects to prevent view controller “bloat”, with better separation of responsibilities between a transitioning delegate object, the animation controller, etc.
For example, maybe:
class ViewControllerB: UIViewController {
let customTransitioningDelegate = PopUpMenuTransitioningDelegate()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
modalPresentationStyle = .custom
transitioningDelegate = customTransitioningDelegate
}
}
And
class PopUpMenuTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PopUpMenuAnimationController(transitionType: .presenting)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PopUpMenuAnimationController(transitionType: .dismissing)
}
}
And
class PopUpMenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case presenting
case dismissing
}
let transitionType: TransitionType
init(transitionType: TransitionType) {
self.transitionType = transitionType
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toVC = transitionContext.viewController(forKey: .to),
let fromVC = transitionContext.viewController(forKey: .from) else { return }
switch transitionType {
case .presenting:
containerView.addSubview(toVC.view)
var rect = fromVC.view.bounds
let menuHeight = fromVC.view.bounds.height / 2
rect.origin.y += fromVC.view.bounds.height
rect.size.height = menuHeight
toVC.view.frame = rect
toVC.view.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
rect.origin.y = menuHeight
toVC.view.frame = rect
toVC.view.alpha = 1
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
case .dismissing:
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
var rect = fromVC.view.frame
rect.origin.y = toVC.view.bounds.height
fromVC.view.frame = rect
fromVC.view.alpha = 0
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
}
That decouples the transitioning delegate and animation controller from the view controller and lets you use this with other view controllers should you ever need to.
Note, I’m also a little more careful about the parameter to completeTransition, too. Once you start playing around with interactive transitions, you might support cancelable transitions.

Related

Unable to access UIView subclass IBOutlets From TabBarController

I have a UIView Class with xib. I try to add it in another ViewControllers as a popover view. I have outlet connections. But when I run the application it crashed and stated
This class is not key value coding-compliant for the key btnAbtUs
I think the problem is should select delegate. I may using wrong way to add this xib. How can I correct it?
Here is my code.
my UIView subclass
class MoreView: UIView {
#IBOutlet var containerView: UIView!
#IBOutlet weak var btnAboutUs: UIButton!
override public func awakeFromNib() {
super.awakeFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
func loadViewFromNib() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "MoreView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.insertSubview(view, at: 0)
commitInit()
}
private func commitInit(){
containerView.translatesAutoresizingMaskIntoConstraints = true
self.btnAboutUs.addTarget(self, action: #selector(self.clickAboutUs(_:)), for: .touchUpInside)
}
class func instanceFromNib() -> UIView {
return UINib(nibName: "MoreView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
#objc func clickAboutUs(_ sender: Any) {
print("tap")
}
}
in UITabBarController
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let moreView = MoreView.instanceFromNib
if let navigationController = viewController as? UINavigationController,
navigationController.viewControllers.contains(where: { $0 is MoreViewController }) {
moreView().frame.origin.y = 100
self.view.addSubview(moreView())
return false
} else {
moreView().removeFromSuperview()
return true
}
}
Finally I found the issue. This is the right way to add a UIView as a SubView controller of a UIViewController.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let navigationController = viewController as? UINavigationController,
navigationController.viewControllers.contains(where: { $0 is MoreViewController }) {
let mySubView : MoreView
mySubView = MoreView(frame: CGRect(x: 0, y: 0, width: 375, height: 667) )
self.view.addSubview(mySubView)
return false
} else {
return true
}
}
You might have copied the XIB and forgot to remove/add a connection with the IBOutlet. Please check.
It indicates that an already connected Interface Builder object is removed/renamed in its owner's source (File's Owner).
You may forgot to remove a connection with the IBOutlet. Please check on on show connection inspector.First click on file inspector of xib then click on show connection inspector. Shown in image.
Remove the broken outlets.

Loading UIView from Bundle only fills screen partially

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)
}
}

Can I Instantiate a Swift Custom UIView from Storyboard Without It Nesting Inside Itself?

Every where I look for how to make a custom UI View with a nib I see the following code to use
class CustomView: UIView {
var contentView: UIView!;
#IBOutlet weak var sampleLabel: UILabel!
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
if self.subviews.count == 0 {
nibSetup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
if self.subviews.count == 0 {
nibSetup()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
nibSetup()
}
}
fileprivate func nibSetup() {
contentView = loadViewFromNib()
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
}
fileprivate func loadViewFromNib() -> TimerView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView
return nibView
}
}
But this basically loads a new instance of the custom view (Loaded from the nib) into a contentView on the original instance. This works until you want to call methods on your view from the view controller it is in. When you want to call a method on the instance from the view controller you are calling it on the original instance not the instance that is in cotnentView so the result is nothing happens.
As a work around I have declared the contentView to be a CustomView instead of a UIView and then my public methods call methods on the content view
ie
class CustomView: UIView {
var contentView: CustomView!;
#IBOutlet weak var sampleLabel: UILabel!
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
if self.subviews.count == 0 {
nibSetup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
if self.subviews.count == 0 {
nibSetup()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
nibSetup()
}
}
fileprivate func nibSetup() {
contentView = loadViewFromNib()
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
}
fileprivate func loadViewFromNib() -> TimerView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! CustomView
return nibView
}
func setLabelText(blah: String) {
sampleLabel.text = blah
}
public func setLabelTextFromParent(words: String) {
contentView.setLabelText(blah: words)
}
}
This seems really hacky though. There has to be a much better way of doing this!
Can someone explain to me how to do this so that I only have one instance of my custom view rather than one nested in another when instantiating it from IB. Thanks.
You can define an extension on UIView like
extension UIView {
class func fromNib<T : UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
I don't see any hack here.
Call it like
let myCustomView: MyCustomView = UIView.fromNib()

Hide Custom View UIButton From UIViewController Class

Actually i have a Custom view with two button, and i want to hide it at runtime through UIViewController , So i don't get any exact thing to hide that button from UIViewcontroller class
Here is my CustomView class,
import UIKit
class BottomButtonUIView: UIView {
#IBOutlet weak var btnNewOrder: UIButton!
#IBOutlet weak var btnChat: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = Bundle.main.loadNibNamed("BottomButtonUIView", owner: self, options: nil)?.first as? BottomButtonUIView {
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
}
#IBAction func btnOrderNowClick(_ sender: Any) {
let VC1 = StoryBoardModel.orderDeatalStoryBord.instantiateViewController(withIdentifier: "NewOrderViewController") as! NewOrderViewController
VC1.isPush = false
let navController = UINavigationController(rootViewController: VC1) // Creating a navigation controller with VC1 at the root of the navigation stack.
let currentController = getCurrentVC.getCurrentViewController()
currentController?.present(navController, animated:true, completion: nil)
}
#IBAction func btnChatNowClick(_ sender: Any) {
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
}
I set it to UIView in StoryBoard, and then I create outlet of that view,
#IBOutlet weak var viewBottmNewOrder: BottomButtonUIView!
Now i want to hide btnNewOrder from UIViewcontroller class but when i use
viewBottmNewOrder.btnNewOrder.isHidden = true it cause null exception, Please do need full answer.
Please don't do like that. The required init(coder aDecoder: NSCoder) will call a lot of times when the BottomButtonUIView created from xib. And your custom view will look like:
[BottomButtonUIView [ BottomButtonUIView [btnNewOrder, btnChat]]].
So when you access to btnNewOrder like that:
viewBottmNewOrder.btnNewOrder it will null.
I think you should add your custom view in viewDidLoad of your `UIViewController'.

Start animation once a UIView appeared on screen

I have a custom UIView that I want to cover the screen once the user taps a button. It kind of simulates a custom view. There is child UIView in the custom UIView that should animate from the bottom (hidden at first) up to it's normal position (visible at the bottom). The problem that I am having is that it seems like layoutSubviews is a bad place to start doing animations. Where would the correct place be? Something like viewDidAppear but for UIViews.
In UIViewController:
let rect: CGRect = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height)
let alertView = AlertView(frame: rect)
view.addSubview(alertView)
In the AlertView:
import UIKit
class AlertView: UIView {
let nibName = "AlertView"
let animationDuration = 0.5
var view: UIView!
#IBOutlet weak var notificationView: UIView!
#IBOutlet weak var notificationBottomConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
viewSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
viewSetup()
}
override func didAddSubview(subview: UIView) {
super.didAddSubview(subview)
}
func viewSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
// move the notification view offscreen
notificationBottomConstraint.constant = -notificationView.frame.size.height
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiateWithOwner(self, options: nil)[0] as! UIView
}
override func layoutSubviews() {
super.layoutSubviews()
print("layoutSubviews")
animate()
}
func animate() {
// move the notification up
self.notificationBottomConstraint.constant = 0
UIView.animateWithDuration(animationDuration) { () -> Void in
self.view.setNeedsDisplay()
}
}
}
I suggest you to call your animate() function from one of these methods:
willMoveToSuperview:
didMoveToSuperview
These methods of UIView as needed to track the movement of the current view in your view hierarchy.
Reference: UIView class
Your final constraint value is not in your animate closure. Try this:
func animate() {
// move the notification up
UIView.animateWithDuration(animationDuration) { () -> Void in
self.notificationBottomConstraint.constant = 0
self.view.setNeedsDisplay()
}
}