Custom xib not sized correctly inside superview - ios

I have a bit of a complicated UI structure.
I have a collection view. For the collection view's cells, I have a custom xib. Inside that custom xib, I load another custom xib into a UIView.
Here is a picture of my collection view cell's custom xib:
https://i.stack.imgur.com/GQLpj.png
I have also highlighted the UIView (Video Container View) that I load another xib into. Here is what that xib looks like:
https://i.stack.imgur.com/jpSi0.png
With the collection view, I do some custom resizing of the cells by making the view 16:9 ratio:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Get the width of the screen
let width = UIScreen.main.bounds.width
let imageWidth = width
let imageHeight = imageWidth / (16 / 9)
self.postMediaHeight = imageHeight
return CGSize(width: imageWidth, height: imageHeight)
}
My PostMediaCollectionViewCell class looks like this:
class PostMediaCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var container: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var moreView: UIView!
#IBOutlet weak var moreViewLabel: UILabel!
#IBOutlet weak var videoContainerView: VideoContainerView!
override func awakeFromNib() {
super.awakeFromNib()
}
func initialize() {
//
}
}
And my VideoContainerView class looks like this:
class VideoContainerView: UIView {
#IBOutlet var rootView: UIView!
#IBOutlet weak var videoView: VideoView!
#IBOutlet weak var playIconContainer: UIView!
#IBOutlet weak var timeContainer: UIView!
#IBOutlet weak var timeLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func initialize() {
Bundle.main.loadNibNamed("VideoContainerView", owner: self, options: nil)
rootView.translatesAutoresizingMaskIntoConstraints = false
addSubview(rootView)
rootView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
rootView.topAnchor.constraint(equalTo: topAnchor).isActive = true
rootView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
rootView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
}
But when I run the simulator, the VideoContainerView doesn't seem to fit the superview's (the cell's) constraints/frame, as you can see in the screenshot below. First, there's a white space above the video, and second, the video's resolution is stretch past the container.
One interesting thing I noticed is that if I scroll down past the cell and then scroll back up, it shows the video perfectly.
What am I doing wrong, and what am I missing here?
Update
I just printed out the bounds of a number of views and noticed that the videoView bounds are different from its parents bounds:
rootview bounds: (0.0, 0.0, 661.0, 410.0)
videoview bounds: (0.0, 0.0, 654.0, 593.0)
What should I do here?

You are missing constraints while adding rootView as subview of VideoContainerView. In initialize add following constraints.
func initialize() {
Bundle.main.loadNibNamed("VideoContainerView", owner: self, options: nil)
rootView.translatesAutoresizingMaskIntoConstraints = false
addSubview(rootView)
rootView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
rootView.topAnchor.constraint(equalTo: topAnchor).isActive = true
rootView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
rootView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}

container is the main view of VideoContainerView
Change initialize method code as below and try to run your code
func initialize() {
let view = Bundle.main.loadNibNamed("VideoContainerView", owner: self, options: nil)?.first as! UIView
addSubview(view)
container.frame = self.bounds
container.autoresizingMask = [.flexibleWidth,.flexibleHeight]
}

Related

Tap Gesture not working for custom view in swift

I have a circleButton, and adding a gesture recognizer to it is not working. circleButton is a UIView.
#IBOutlet private weak var circleButton: UIView!
Here is my code:
class CustomBottomBar: UIView {
#IBOutlet private weak var circleButton: UIView!
#IBOutlet private weak var bottomBarView: UIView!
#IBOutlet private weak var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func layoutSubviews() {
super.layoutSubviews()
circleButton.layer.borderColor = UIColor.black.cgColor
circleButton.layer.borderWidth = 2
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
#objc func onAddButtonClicked() {
print("add")
}
private func commonInit() {
if let customBar = Bundle.main.loadNibNamed("CustomBottomBar", owner: self, options: nil)?[0] as? UIView {
customBar.frame = bounds
addSubview(customBar)
circleButton.layer.cornerRadius = circleButton.frame.height/2
circleButton.clipsToBounds = true
circleButton.isUserInteractionEnabled = true
print(self.circleButton.frame.size)
print(self.contentView.frame.size)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onAddButtonClicked))
circleButton.addGestureRecognizer(tapGesture)
}
}
}
The frame size is also coming properly for circleButton. Not sure what is the problem. I am using customBottomBar in a storyboard. Here is the full code :
https://github.com/hirakjyotiborah/AwesomeTaskManag3r
Inside your Main.storyboard give a height constraint to your bottom Bar as you only set leading , trailing and bottom or do this inside commonInit
// 70 (Bottom bar) + 80/2 (half of circle button) = 110
heightAnchor.constraint(equalToConstant: 110).isActive = true
As the base for responding to any click is the frame and the main parent view of the button has zero height

How to use custom view with in swift?

I want to add a custom view in TableViewHeader. But when I run the following code it creates a Cycle and app stuck for any user interaction.
import UIKit
class ExpandableView: UIView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
var contentView: UIView!
#IBOutlet weak var checkMarkView: UIImageView!
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
public override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
}
private func setUpView() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "ExpandableView", bundle: bundle)
self.contentView = nib.instantiate(withOwner: self, options: nil).first as? UIView
addSubview(contentView)
contentView.center = self.center
contentView.autoresizingMask = []
contentView.translatesAutoresizingMaskIntoConstraints = true
}
}
I am using it as follows:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let frame = CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 60)
let expandabelView = ExpandableView(frame: frame)
return expandabelView
}
And it shows following error on run.
There may be many other ways, but I recommend you to make your ExpandableView reusable to improve performance.
First of all, simplify your ExpandableView class:
import UIKit
class ExpandableView: UITableViewHeaderFooterView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
#IBOutlet weak var checkMarkView: UIImageView!
}
Please do not miss that the superclass is UITableViewHeaderFooterView, not UIView.
Second, check the settings of your ExpandableView.xib:
The Custom View setting of the defined view needs to be ExpandableView.
When you cannot choose ExpandableView from the pull down list, you may need to input manually. Do not forget to check Inherit Module From Target.
The Custom View setting of the File's Owner needs to be empty.
If there's some class already set, remove it manually.
Confirm all Outlets are connected properly to your ExpandableView.
(You should better reconnect them all, after you modified your xib.)
You may need to re-structure your view hierarchy and/or constraints.
Third, modify your view controller holding the table view.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
//...
let nib = UINib(nibName: "ExpandableView", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "ExpandableView")
tableView.estimatedSectionHeaderHeight = 60
tableView.sectionHeaderHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let expandabelView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "ExpandableView")
// Frame size should be represented with constraints.
return expandabelView
}
When you create custom UIView, it should follow this if you want to use it init with frame.
class CustomView: UIView {
//This should be contentview of your xib
#IBOutlet var view: UIView!
let nibName = "CustomView"
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
private func xibSetup()
{
Bundle.main.loadNibNamed(nibName, owner: self, options: nil)
self.view.autoresizesSubviews = true
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.view)
self.view.frame = self.frame
}
}
Since you need to load view from nib, just load it and then add subview to your view. Then set content view's frame and set autoresizing mask of content view correctly
private func setUpView() {
Bundle.main.loadNibNamed("ExpandableView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}

custom inputAccessoryView as UIView from nib giving error: returned 0 width, assuming UIViewNoIntrinsicMetric

I am using an inputAccessoryView which is loaded from a nib using the following UIView subclass:
class CustomInputAccessoryView: UIView {
#IBOutlet var containerView: UIView!
#IBOutlet var contentView: UIView!
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var textView: UITextView!
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
override init(frame: CGRect) {
super.init(frame: frame)
setupInputAccessoryView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupInputAccessoryView()
}
func setupInputAccessoryView() {
self.translatesAutoresizingMaskIntoConstraints = false
Bundle.main.loadNibNamed("CustomInputAccessoryView", owner: self, options: nil)
addSubview(containerView)
containerView.frame = self.bounds
}
}
I assign the view to the inputAccessoryView and set it up just by the following:
var customInputAccessoryView = CustomInputAccessoryView()
The inputAccessoryView loads properly and displays correctly. The problem is when I press inside the textView of the inputAccessoryView and the keyboard displays, I get the following error:
<_UIKBCompatInputView: ...; frame = (0 0; 0 0); layer = <CALayer: ...>> returned 0 width, assuming UIViewNoIntrinsicMetric
I have two files, a .swift file which is the subclass of UIView displayed above and the nib file by the same name. In the file owner of the nib I select the sub class (CustomInputAccessoryView) and wire up the outlets.
I don't really know what is happening other than maybe the input accessory view automatically resizing to zero frame whenever the keyboard is displayed before going to the correct size. If so I'm assuming I need to change some AutoLayout priorities but what I have tried is not working. The only hard width constraints are the buttons which have fixed widths.
The inputAccessoryView width should always be the width of the window it is displayed in.

Resize a UIView inside a stackview

I need help to accomplish this:
Its a group of 3 photos. The first photo is the button screen before the click, the second photo is the screen after the click and the third photo is the way i've designed in the IB using stackview
I've being trying to create this and this is the result i've got so far. I still haven't created anything as the After button click image shows because of this:
My Result now
As you can see in the gif when i press the button, the UIView height constraint.constant is set to a higher value and the UIView get higher. All i want is the stripped background to get higher as well.
This is the way the view is disposed in IB.
And finally, this is the way i've coded
class LetterScreenViewController: UIViewController, ResizeWordViewDelegate {
#IBOutlet weak var youTubeView: YouTubeView!
#IBOutlet weak var phonemeView: PhonemeView!
#IBOutlet weak var wordView: WordView!
var letterPresenter: LetterPresenter?
#IBOutlet weak var heightWordViewConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
delegateWordObj()
if let presenter = letterPresenter {
presenter.setupView()
}
}
#IBAction func dismissLetter(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func delegateWordObj() {
wordView.resizeWordViewDelegate = self
}
func didPressButton() {
if heightWordViewConstraint.constant <= 0 {
heightWordViewConstraint.constant = 30
}else{
heightWordViewConstraint.constant = 0
}
}
}
import UIKit
protocol ResizeWordViewDelegate {
func didPressButton()
}
class WordView: UIView {
#IBOutlet weak var backgroundListras: UIImageView!
#IBOutlet var WordView: UIView!
#IBOutlet weak var showWordButton: UIButton!
var resizeWordViewDelegate: ResizeWordViewDelegate!
override init(frame: CGRect) {
super.init(frame: frame)
xibInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibInit()
}
func xibInit() {
Bundle.main.loadNibNamed("WordView", owner: self, options: nil)
addSubview(WordView)
WordView.frame = self.bounds
WordView.round(corners: [.topLeft,.topRight], radius: 120)
WordView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
#IBAction func showWordButtonAction(_ sender: Any) {
resizeWordViewDelegate.didPressButton()
self.frame.size = CGSize(width: 375, height: 200)
self.backgroundListras.frame.size = CGSize(width: 375, height: 200)
}
}
I want to know a way to resize this UIView with all the content in it. After finding a solution to increase the height i'll be able to put all the others components shown when i press the button.
I believe that your goal is to create something like this:
All I had to do was change the height of the constraint programmatically
Check the solution in the project below:
https://gitlab.com/DanielLimaDF/ResizeTest.git
Important: Beware of addSubview, it can remove constraints from a View if the constraints were created before calling addSubview

UIView doesn't center properly in UIScrollView with autolayout

I'm making an application where I need an x amount of custom UIView and place them in a scrollView. The layout of the scrollView and the UIView xib are set with AutoLayout. The result I'm getting now is this:
First View is well centred in ScrollView
Second View is wrong and has a lot of space in between
See my VC Code under here.
let sponsors = Sponsors.createSponsors() <-- Array
override func viewDidLoad() {
super.viewDidLoad()
configureSponsors()
}
//MARK: Load the AddView in ScrollView
func configureSponsors() {
self.scrollView.contentSize = CGSizeMake(CGFloat(sponsors.count) * self.scrollView.frame.size.width, self.scrollView.frame.size.height)
for sponsor in sponsors {
numberOfItems++
let addView = NSBundle.mainBundle().loadNibNamed("AddView", owner: self, options: nil).last as! AddView
addView.addDataToAddView(sponsor)
addView.frame = CGRectMake(CGFloat(numberOfItems - 1) * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height)
self.scrollView.addSubview(addView)
}
}
And here is my UIView code:
//MARK: Outlets
#IBOutlet weak var backgroundImageView: UIImageView!
#IBOutlet weak var sponsorTitle: UILabel!
#IBOutlet weak var sponsorLogo: UIImageView!
#IBOutlet weak var sponsorSubtitle: UILabel!
#IBOutlet weak var roundView: UIView!
#IBOutlet weak var playMusicButton: UIButton!
//MARK: Properties
var cornerRadius: CGFloat = 3.0
func addDataToAddView(sponsor: Sponsors) {
backgroundImageView.image = UIImage(named: sponsor.backgroundImage)
sponsorLogo.image = UIImage(named: sponsor.logoImage)
sponsorTitle.text = sponsor.title
sponsorSubtitle.text = sponsor.subTitle
}
override func layoutSubviews() {
roundView.layer.cornerRadius = roundView.frame.size.width / 2
roundView.alpha = 0.7
roundView.clipsToBounds = true
}
//MARK: PlayVideo
#IBAction func playVideo(sender: UIButton) {
//play music
}
I've already searched for the same problems. I found out that I have to use the Pure Auto Layout Approach. Should that mean I need to programatically set the constraints for the UIView?
Many thanks,
Dax
Update:
Pictures for scrollView setup:
You should not perform layout calculations in viewDidLoad as frames are not set correctly till then.
Correct place to do this is either viewWillLayoutSubviews or viewDidLayoutSubviews as you will get the correct frame data when these functions are called

Resources