I have a custom xib that has a horizontal stack view with 3 child views. I use this view in other xibs. When the user clicks on one of these child views, I scale it using:
view.transform = CGAffineTransform(scaleX: 2, y: 2)
When the view is scaled, it's actually meant to appear out of the parent view's bounds. However, when I click the child view, it scales it fine, but the view is cut off.
I've double checked that all of my views in the xib have clipsToBounds set to false, but it's still cutting it off.
Here is what my xib class looks like:
class CustomView: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func initialize() {
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
... some other things that I've left out that aren't important ...
}
Is there something I'm missing? How do I make the child views not get clipped/cut off?
What about the view hosting the XIB? (CustomView in this case). Set clipsToBounds=false in your initialize. Otherwise, take a look at Xcode's view hierarchy debugger and see if there's another view that causing the clipping.
Related
I am trying to create a view using xib and dynamically presenting in the viewController. I am passing the frame parameters while setting up the view :
let frame = CGRect(x: 0, y: 171, width: self.view.bounds.width, height: self.view.bounds.height - 327)
self.xibView = xibView(frame: frame)
self.view.addSubview(self.xibView)
self.xibView.delegate = self
in the Xib view My structure looks like this :
I have added stackView so that the content resizes automatically based on frame size that we provide. On the code, we are doing :
class xibView: UIView {
// some other declarations
#IBOutlet weak var sharpBtn: UIView!
#IBOutlet weak var contentView: UIView!
public var delegate: xibViewDelegate?
override init(frame: CGRect){
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
// write view stuff here
Bundle.main.loadNibNamed("xibView", owner: self, options: nil)
addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = true
}
.
.
.
}
Here contentView is the view below safe area that we have in the hierarchy.
When I am trying to run it, The descriptor for the view gives me :
<UIView: 0x7fd5c54077a0; frame = (0 0; 835 867); autoresize = RM+BM; layer = <CALayer: 0x600001708500>>
This is the size of freeform xib that I have created. I am not sure if there is something that I am missing to make the view resize to the frame that we provide.
Can anyone help?
For the update I was able to fix the following issue by assigning the frame to the view in layoutSubviews function.
override func layoutSubviews() {
super.layoutSubviews()
// we need to adjust the frame of the subview to no longer match the size used
// in the XIB file BUT the actual frame we got assinged from the superview
self.contentView.frame = self.bounds
}
and then setting the translatesAutoresizingMaskIntoConstraints to true in the init function :
contentView.translatesAutoresizingMaskIntoConstraints = true
I have a UICollectionViewController embedded inside a UINavigationController which in turn embedded inside a UITabBarController.
I want to add a UIView to the UICollectionViewController just above the tab bar (shown by red rectangle).
I have the UIView created separately as a nib file.
import UIKit
class BottomView: UIView {
#IBOutlet var view: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
fileprivate func commonInit() {
Bundle.main.loadNibNamed("BottomView", owner: self, options: nil)
view.frame = self.frame
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(view)
}
}
And I initialize and add it in the UICollectionViewController like so.
class CollectionViewController: UICollectionViewController {
fileprivate var bottomView: BottomView!
override func viewDidLoad() {
super.viewDidLoad()
let yPos = view.bounds.height - (tabBarController!.tabBar.frame.size.height + 44)
bottomView = BottomView(frame: CGRect(x: 0, y: yPos, width: view.bounds.width, height: 44))
collectionView?.addSubview(bottomView)
}
// ...
}
I figured if I substract the combined height of the bottom view plus the tab bar from the entire view's height, I should be able to get the correct y position value. But it's not happening. The view is getting added but way off screen.
How do I calculate the correct y position without hardcoding it?
Example demo project
I would suggest adding the BottomView to the UICollectionViewController's view rather than to the collection view itself. This is part of the problem you're having.
You're also trying to set the frame of the BottomView in the viewDidLoad() method using values from view.bounds. The CGRect will return (0.0, 0.0, 0.0, 0.0) at this point because the layout has yet to take place, which is most likely why your positioning is off. Try moving your layout logic to the viewWillLayoutSubviews() method and see if that helps.
A better approach would be by setting auto layout constrains rather than defining a frame manually, this will take a lot of the leg work out for you.
Here's a quick example:
self.bottomView.translatesAutoresizingMaskIntoConstraints = false
self.view.insertSubview(self.bottomView, at: 0)
self.bottomView.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor).isActive = true
self.bottomView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.bottomView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
self.bottomView.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
You can apply autolayout logic in your viewDidLoad() and it should work correctly.
You can find some more information on setting autolayout constraints programatically here:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
Sounds what you want to achieve is exactly the footer view for the UICollectionView.
A footerView is like a view that will stick to the bottom of the collectionView and wont move with the cells.
This will help you add a footer View: https://stackoverflow.com/a/26893334/3165112
Hope that helps!
Here i have created UIView subclass named as CustomSlider. CustomSlider added on Storyboard and connected with storyboard outlet reference. When i am printing the bounds, I am having issues with printing the bounds, its providing incorrect console output.
class CustomSlider: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
func setupView() {
print("self.bounds.height \(self.bounds)" )
self.backgroundColor = UIColor.red
}
}
class ViewController: UIViewController {
#IBOutlet weak var slider: CustomSlider?
override func viewDidLoad() {
super.viewDidLoad()
slider?.setupView()
}
}
When i am trying to build the output bounds displaying as below :
self.bounds.height (0.0, 0.0, 1000.0, 1000.0)
But i'm setting constraints trailing, top, right, height. I have tried below line also.
slider?.translatesAutoresizingMaskIntoConstraints = false
here is screenshot my project looks like
My expected console output would be the slider bounds.
Your call to print the bounds comes too soon. A view being loaded from a xib/storyboard has bogus bounds (as shown) until layout has occurred. Layout has not occurred at the time of viewDidLoad; the view is not yet in the interface and no sizes are real yet. Postpone your access to bounds until at least after viewDidLayoutSubviews has been called for the first time.
Is it possible to create reusable stack views on the story board that can be used dynamically to be generated at a later time? Sort of a template/widget/component.
I am aware that I can do this with a class but if I am able to visually generate a set of components that can be re-used at a latter time I may be able to let our designers make changes to storyboards directly.
Yes -- you can do this with any UIView. There are many tutorials for this (e.g. http://onedayitwillmake.com/blog/2013/07/ios-creating-reusable-uiviews-with-storyboard/)
The basic idea is to drag one onto Storyboard or XIB, make a custom class for it, then implement the view's awakeFromNib to load it.
Yes.It is.
Create a empty xib and then add a stack view to it.
Then create a class which extends UIStackView.
class stackView: UIStackView {
var contentView : UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init(coder: NSCoder) {
super.init(coder: coder)
xibSetup() }
func xibSetup() {
contentView = loadViewFromNib()
contentView.frame = bounds
contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(contentView)
}
func loadViewFromNib() -> UIStackView! {
let view: UIStackView? = Bundle.main.loadNibNamed("stackView", owner: nil, options: nil)?.first as! UIStackView?
return view
}
Create a viewController.Add a stackView to it.In StackView properties, goto 3rd bar which named as custom class, for class name give stackView class name
I have some weird behavior related to frame sizes that I can't fix after hours of trying different things. This just doesn't make any sense.
I have a custom UIView and a related .xib file:
ErrorView.swift
import UIKit
class ErrorView: UIView {
#IBOutlet weak var labelErrorTitle: UILabel!
#IBOutlet weak var view: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
func setupView() {
NSBundle.mainBundle().loadNibNamed("ErrorView", owner: self, options: nil)
self.addSubview(view)
}
ErrorView.xib (using inferred size, the label is centered using constraints)
I want to add this view to a custom UITableView, at the bottom. I want to make it slim, with a height of 45 and a width of the view screen width. I want to add it to the bottom.
Very easy!! I just set the size with a frame like this:
class LoadingTableView: UITableView {
var errorView: ErrorView = ErrorView () // Here is the Error View
var errorFrame: CGRect! // The frame (size) I will use
required init(coder aDecoder: NSCoder) {
}
override func awakeFromNib() {
super.awakeFromNib()
// I create the frame here to put the error at the top
errorFrame = CGRectMake(0,0,self.frame.width,45)
// Init the ErrorView
errorView = ErrorView(frame: errorFrame)
// I add the subview to root (I have this rootView created)
rootView?.addSubview(errorView)
}
// This is needed, it updates the size when layout changes
override func layoutSubviews() {
super.layoutSubviews()
// Create the size again
errorFrame = CGRectMake(0,0,self.frame.width,45)
// I update the frame
errorView.frame = frame
}
This should work but it doesn't. The size is just weird. It takes the size from the nib which is 320x568. Then it just moves the frame, but the size doesn't change.
And here comes the great part. If I set the errorView.frame size to .frame, which is the frame of the tableView then it works! With orientation changes and all!
But as long as I change the frame to a custom size, whatever is in awakeFromNib or layoutSubviews it doesn't and starts to act weird.
Why does it keeps the size of the nib? And why if I put .frame it works at it should but a custom size doesn't? It looks like I'm super close, it's frustrating as hell.
The objective is to say tableView.error("errorCode") and then that errorView appears. And it works on all devices and orientations.
Instead of adding a subview to UITableView, use it's tableFooterView and tableHeaderView properties:
Replace
rootView?.addSubview(errorView)
With:
tableFooterView = errorView