How to load Xib to CollectionView using width constraint in Swift? - ios

I have collectionView like this
I'm calling different 3 xib files to cells like this
collectionView.register(UINib(nibName: "OfficialVehicleHeader", bundle: nil), forCellWithReuseIdentifier: "OfficialVehicleHeaderCell")
collectionView.register(UINib(nibName: "OfficialVehicleMedium", bundle: nil), forCellWithReuseIdentifier: "OfficialVehicleMediumCell")
collectionView.register(UINib(nibName: "OfficialVehicleBottom", bundle: nil), forCellWithReuseIdentifier: "OfficialVehicleBottomCell")
My collectionView layout code
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
collectionView.setCollectionViewLayout(layout, animated: true)
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
In the xib file i set constraint (leading, trailing, bottom, top as 0) in addition height constraint. The height is running awesome no problem but i don't want to set width constraint. Because there is different between screens
Where did i mistake ?

You have to set leading and trailing it will set width according to devices width. But don't forget to make your collection views constraints to be resizable.
Here is my sample code on how to do it:
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
collectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 140).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
layout settings:
layout.itemSize = CGSize(width: self.view.frame.width - 10, height: self.view.frame.height / 3)
Cells constraints to be resizable:
cell.widthAnchor.constraint(equalTo: widthAnchor, constant: 0).isActive = true
cell.heightAnchor.constraint(equalTo: heightAnchor, constant: 0).isActive = true
cell.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
cell.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true

I solved the problem. I need to set width constraint but i don't want to set. I solve this way
Go to xib file and set width constraint
Go to size inspector and select that you created width constraint
Set identifier for example "myConstraint"
Open your cell file and define IBOutlet
for constraint in self.containerView.constraints {
if constraint.identifier == "myConstraint" {
constraint.constant = UIScreen.main.bounds.size.width * 0.70
}
}
containerView.layoutIfNeeded()
I added image that i mentioned

Related

How to make UIView which is inside scrollview adapt to screen orientation when user changes screen from portrait to landscape in swift

How to make UIView which is inside scrollview adapt to screen orientation when user changes screen from portrait to landscape in swift?
var scrollView: UIScrollView = {
var scroll = UIScrollView()
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
scrollView.heightAnchor.constraint(equalToConstant: 500).isActive = true
for i in 0..<arr.count {
var contentView = UIView()
contentView.frame = CGRect(x: i * Int(view.bounds.size.width) + 10, y: 0, width: Int(view.bounds.size.width) - 20 , height: Int(view.frame.height))
scrollView.contentSize = CGSize(width: (view.frame.size.width * CGFloat((Double(i)+1))) ,height: scrollView.frame.size.height)
}
Image
You really want to be using auto-layout instead of trying to calculate frame sizes. Let it do all the work for you.
Based on your code, it looks like you want each "contentView" to be the width of the scrollView's frame, minus 20 (so you have 10-pts of space on each side).
You can quite easily do this by embedding your contentViews in a UIStackView.
Here's a simple example:
class ViewController: UIViewController {
var scrollView: UIScrollView = {
var scroll = UIScrollView()
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
// use a stack view to hold and arrange the scrollView's subviews
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 20
// add the stackView to the scrollView
scrollView.addSubview(stackView)
// respect safe area
let safeG = view.safeAreaLayoutGuide
// use scrollView's Content Layout Guide to define scrollable content
let layoutG = scrollView.contentLayoutGuide
// use scrollView's Frame Layout Guide to define content height (since you want horizontal scrolling)
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 100),
// you're setting leading and trailing, so no need for centerX
//scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: 0),
// let's constrain the scrollView bottom to the view (safe area) bottom
//scrollView.heightAnchor.constraint(equalToConstant: 500),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -10.0),
// constrain Top and Bottom of the stackView to scrollView's Content Layout Guide
stackView.topAnchor.constraint(equalTo: layoutG.topAnchor),
stackView.bottomAnchor.constraint(equalTo: layoutG.bottomAnchor),
// 10-pts space on leading and trailing
stackView.leadingAnchor.constraint(equalTo: layoutG.leadingAnchor, constant: 10.0),
stackView.trailingAnchor.constraint(equalTo: layoutG.trailingAnchor, constant: -10.0),
// constrain stackView's height to scrollView's Frame Layout Guide height
stackView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
// add some views to the stack view
let arr: [UIColor] = [
.red, .green, .blue, .yellow, .purple,
]
for i in 0..<arr.count {
let contentView = UIView()
contentView.backgroundColor = arr[i]
stackView.addArrangedSubview(contentView)
// constrain each "contentView" width to scrollView's Frame Layout Guide width minus 20
contentView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: -20).isActive = true
// don't do this
//contentView.frame = CGRect(x: i * Int(view.bounds.size.width) + 10, y: 0, width: Int(view.bounds.size.width) - 20 , height: Int(view.frame.height))
// don't do this
//scrollView.contentSize = CGSize(width: (view.frame.size.width * CGFloat((Double(i)+1))) ,height: scrollView.frame.size.height)
}
}
}
Run that and see if that's what you're going for.
You need to add your subviews to the scroll view and setup their constraints - using of an auto-layout. Don't use contentView.frame = CGRect(...) and scrollView.contentSize = CGSize(...).
For example you can change your for-in to this:
Note: this is only example, change your for-in loop to your needs.
for i in 0..<arr.count {
// we need to distinguish the first and last subviews (because different constraints)
let topAnchor = i == 0 ? scrollView.topAnchor : scrollView.subviews.last!
let isLast = i == arr.count - 1
// here we will use a specific height for all subviews except the last one
let subviewHeight = 60
var contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
if isLast {
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
]
} else {
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.heightAnchor.constraint(equalToConstant: subviewHeight),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
]
}
}

dynamic height of UICollectionView (horizontal scrolling) based on cell height

I've got the following situation:
A collectionView inside a containerView that sits below the navigationBar. The view is set up programmatically (no xib).
I want to place the containerView in a way that it does not need a height constraint. Is that possible? The containerView should get it's height based on the constraints in the collectionView cells.
Code:
This is the function which lays out the containerView inside the viewController:
private func setUpContainerView() {
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20)
])
}
Inside the containerView (which is a subclass of UIView), the collectionView is set up like that (called in the init of that class):
private func setUpCollectionView() {
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: collectionViewFlowLayout())
guard let collectionView = collectionView else { return }
addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
collectionView.topAnchor.constraint(equalTo: self.topAnchor)
])
collectionView.delegate = self
collectionView.dataSource = self
registerCollectionViewNibs()
}
private func collectionViewFlowLayout() -> UICollectionViewFlowLayout{
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumInteritemSpacing = 10
flowLayout.minimumLineSpacing = 10
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
return flowLayout
}
When I do it like that, the collectionView is not visible. It only shows up when I add a height constraint with a specific constant to the containerView (inside the setUpContainerView function).
The problem is, that I don't know that height. I would have to set that height to 10+10+50 = 70 manually. But I want to make the containerView get it's size based on the collectionView cell constraints. Is that possible?

addSubview and addArrangedSubview not showing content

I'm adding a UILabel to a UIView and then adding the UIView to a UIStackView, but the UILabel is not showing.
override func viewDidLoad() {
super.viewDidLoad()
configureStackView()
label.text = "test"
containerView.addSubview(label)
stackView.addArrangedSubview(containerView)
view.addSubview(stackView)
setStackViewConstraints()
}
func configureStackView() {
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
}
func setStackViewConstraints() {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
I tried setting some sort of size for the view, but doesn't work:
containerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
or setting constraints to the label to the containerView for widthAnchor, leadingAnchor, or trailingAnchor, but that doesn't work either.
UIView on its own does not have an intrinsicContentSize, which UIStackView uses to determine the size of the view when placed in the stackView. Either set constraints for the four edges of the label to the edges of the container
label.topAnchor.constraint(container.topAnchor, constant: myTopConstant).isActive = true
label.leading....
label.bottom....
label.trailing....
label.setContentHuggingPriority(to: .defaultLow, axis: .vertical)
(above, relax the contentHuggingPriority if you are not placing any other views in the stackView; since it has a fixed height and distribution = .fill, your stackView will try to stretch the arrangedViews to fit the height. If the label/container are the only candidates, you'll be breaking constraints with a .required contentHugging on the label)
or give the container a well-defined height (since the axis of your stackView is vertical)
containerView.heightAnchor.constraint(equalTo: ....).isActive = true
Also, in your container, set
translatesAutoresizing... = false

Set constraint on label from below collection view programmatically

Set constraint lbl_Title from bottom to collectionView.
On setting the bottom constraint 60, the label goes below the collection view, after setting it to -60 then it's adjusted to location.
How to set constraints based on collection?
func setCollectionViewConstraints() -> Void {
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 10).isActive = true
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
collectionView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
}
func setRecentJobLabelConstraints() -> Void {
lbl_Title.translatesAutoresizingMaskIntoConstraints = false
lbl_Title.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -60).isActive = true
lbl_Title.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
lbl_Title.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
}
Here the issue is fixed if the constraint is set to -60, I think it's the wrong way.
Setting -60 is the right way. The coordinate system for CocoaTouch is a bit strange because it's (0,0) is in the top-left corner of the device, compared to the coordinated in Cocoa which starts from bottom-left. You'll get used to this once you do more auto-layout programmatically.
Note: Also, you need to give negative values when trying to constraint sub-views to super-views from right.
Different Approach: Another approach would be to constraint the super-view to the sub-view this way it's more readable and self-explanatory. Constraint the bottomAnchor of super-view to sub-view's bottomAnchor with a padding of 60 points.
bottomAnchor.constraint(equalTo: lbl_Title.bottomAnchor, constant: 60).isActive = true
It is not a wrong way , calling the constant while using bottom & trailing constraints should be with a minus value , you can use the below extension i created rather than repeating the same autolayout lines over & over
// MARK: - Anchors Method
extension UIView {
func anchors (top:NSLayoutYAxisAnchor? , leading:NSLayoutXAxisAnchor? , bottom : NSLayoutYAxisAnchor? , trailing: NSLayoutXAxisAnchor? , padding : UIEdgeInsets = .zero){
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top , constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading , constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom , constant: -padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing , constant: -padding.right).isActive = true
}
}
}
and call it like below this :
YourUIView.anchors(top: View.topAnchor , leading: View.leadingAnchor , bottom: View.bottomAnchor , trailing: View.trailingAnchor , padding: .init(top: 10, left: 10, bottom: 10, right: 10))
A quick advice , there is no need to assign a void as a return since the function is not returning something .

What is the difference of implementation between UITableView & UICollectionView when using Xib?

I try to implement TableView and CollectionView with Xib Files. I implement UITableview properly but when I try to implement ColletionView I get an error "libc++abi.dylib: terminating with uncaught exception of type NSException". I got this error in var collectionView = UICollectionView() line.
Why I dont get this error this line var tableView = UITableView() ? And where is my mistake ? And What is the solution ? Thanks in advance.
var tableView = UITableView()
var collectionView = UICollectionView()
override func viewDidLoad() {
super.viewDidLoad()
let headerMenu = UIView()
headerMenu.translatesAutoresizingMaskIntoConstraints = false
headerMenu.backgroundColor = .green
self.view.addSubview(headerMenu)
headerMenu.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
headerMenu.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
headerMenu.topAnchor.constraint(equalTo: view.safeTopAnchor).isActive = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
headerMenu.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: headerMenu.topAnchor, constant: 0).isActive = true
collectionView.bottomAnchor.constraint(equalTo: headerMenu.bottomAnchor, constant: -10).isActive = true
collectionView.rightAnchor.constraint(equalTo: headerMenu.rightAnchor, constant: -10).isActive = true
collectionView.leftAnchor.constraint(equalTo: headerMenu.leftAnchor, constant: 10).isActive = true
collectionView.register(UINib(nibName: "MenuCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "MenuCollectionViewCell")
let mainView = UIView()
mainView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(mainView)
mainView.topAnchor.constraint(equalTo: headerMenu.bottomAnchor, constant: 0).isActive = true
mainView.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: 0).isActive = true
mainView.rightAnchor.constraint(equalTo: view.safeRightAnchor, constant: 0).isActive = true
mainView.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 0).isActive = true
mainView.backgroundColor = UIColor.white
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.estimatedRowHeight = UITableViewAutomaticDimension
mainView.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: mainView.topAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: mainView.bottomAnchor, constant: -10).isActive = true
tableView.rightAnchor.constraint(equalTo: mainView.rightAnchor, constant: -10).isActive = true
tableView.leftAnchor.constraint(equalTo: mainView.leftAnchor, constant: 10).isActive = true
tableView.separatorStyle = .none
tableView.register(UINib(nibName: "ProductTableViewCell", bundle: nil), forCellReuseIdentifier: "ProductTableViewCell")
}
You need to use other init to create UICollectionView instance:
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: rect, collectionViewLayout: layout)
UICollectionView requires a UICollectionViewLayout in order to render. When creating a collection view via the storyboard this is already set with a default value.
You can initialise it like this:
let flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout)
Flow layout
A concrete layout object that organizes items into a grid with optional header and footer views for each section.
The flow layout is one of the default/built in options and is useful for creating a 'bookshelf' style grid in which the columns fill left to right and then top to bottom (unless you change the width).
There is a good tutorial on the Ray Wenderlich website which explains more about collection views and their layouts

Resources