I want a UILabel to appear in the center of my Collection View with a background color. However, when I drag one onto the view in my Storyboard and set its background color it seems to take up the whole view. Instead I want it to just take up a small rectangular section that just surrounds its text within. Here's what I have and what I want it to look like:
Current:
Goal:
Is there any way to set the labels height and width manually? I want it to continue its current behavior of appearing over the cells that get generated in the collection view, but to only take up a limited amount of space instead of taking up the whole view.
EDIT:
Thanks to #robmayoff for commenting that this solution is not possible in case of collection view controller.
This Answer is applicable only if you have a regular view controller but not a collection view controller.
The reason why I'm didn't delete it because -hopefully- it might be useful for some of other viewers.
I suggest to:
Let the label behind the collectionView:
From here (document outline):
Drag the label to be on top of the collectionView (not as a subview). Make sure that both of the components at the same hierarchy, but the label is behind the collectionView. Once you've done this,
Double click on the label (also from the document outline):
Now you are able to move it to the center of the view (by using the arrows on the keyboard for example), make sure to let it be at the center of the screen.
Add the appropriate constraints to let the label to be always in the center of screen.
Make the background of collectionView transperent (clear color).
That's it!
I hope this helps.
Please follow the below steps:
Create an extension of CollectionView
Add the Below function in it.
extension UICollectionView {
func setMessage(_ message: String) {
let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height))
messageLabel.text = message
messageLabel.textColor = .black
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center
messageLabel.sizeToFit()
self.backgroundView = messageLabel
}
func removeMessage() {
self.backgroundView = nil
}
}
If you want to remove that message then just use the above removeMessage() function.
Usages
To set message:
self.collcetionview.setMessage("message")
To remove the message:
self.collcetionview.removeEmptyMessage()
Drag a UIView and place it in the collection view to cover it completely.
Drag a UILabel and give its height and width constraint in storyboard and also set constraint for centre in horizontally and vertically.
You also need to set the height and width of collection view in code as shown below
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
Related
I found the next answer to make UIView's height match its content https://stackoverflow.com/a/39527226/7767664
I tested it, it works fine (if UIView height size in storyboard bigger or smaller than its content then during runtime it autoresize itself to match the content).
But if I use UICollectionViewCell instead of UIView then nothing changes, height of cell is never changed, it always has the hardcoded height we have in storyboard properties:
What else can I do?
Also I have 2 sections in UIControllerView (2 columns).
Two cells in one row should have the same size even if their size content is different (something like this implemented in Android natively when using RecyclerView with GridLayoutManager, very easy)
Update
It works with UIView because I set its top constraint to Safe Are'a top
I can't do it with UICollectionViewCell
Update 2
It seems I have some progress with this answer https://stackoverflow.com/a/25896386/7767664
But instead of newFrame.size.width = CGFloat(ceilf(Float(size.width))) I need newFrame.size.height = CGFloat(ceilf(Float(size.height)))
and when we use this solution, don't add any constraints to cell's bottom otherwise it will not work
With this solution I can't really use any margins otherwise some part of becomes invisible at the bottom of cell
I guess it can be solved with this question https://stackoverflow.com/a/31279726/7767664
You can try with this function called inside you collectionView Extension or inside your native collectionViewController class:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
//return something like the size. I write you an example how to use it.
// You can easily change the value according to your stuff contents height.
return CGSize(width: collectionView.frame.size.width/3, height: 100)
}
I solved my issue thanks to https://stackoverflow.com/a/25896386/7767664 and https://stackoverflow.com/a/31279726/7767664
I decided to put one UIView with all needed child views inside it to UICollecitonViewCell
I set UIView trailing, leading, top to ICollecitonViewCell but I didn't set bottom to cell view (you should use the latest child view inside UIView and connect their bottoms, not with the cell view)
Then I added reference of UIView to my custom cell class and I use its height for cell's height:
public class MyCustomItemCell: UICollectionViewCell {
// other child views references ...
#IBOutlet weak var wrapperView: UIView! // view which contains your other views
//forces the system to do one layout pass
var isHeightCalculated: Bool = false
override public func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//Exhibit A - We need to cache our calculation to prevent a crash.
if !isHeightCalculated {
setNeedsLayout()
layoutIfNeeded()
var newFrame = layoutAttributes.frame
newFrame.size.height = wrapperView.frame.height // use height of our UIView
layoutAttributes.frame = newFrame
isHeightCalculated = true
}
return layoutAttributes
}
}
I have a horizontal collection view, which cells don't cover the whole screen. I want to insert a UIView to a view controller's hierarchy and place it right on top of the selected cell.
In order to do that I need the frame of the selected cell. To get it I've tried the following inside the didSelectItem method:
1)
let frame = collectionViewLayout.layoutAttributesForItem(at: indexPath)?.frame
2)
let selectedCell = collectionView.cellForItem(at: indexPath)
In both cases I get the same result and that's the right one. For inserting a UIView into the hierarchy I use the following code:
self.view.insertSubview(mimicView, aboveSubview: collectionView)
mimicView.frame = frame
print ("mimicView frame:", mimicView.frame)
The print statement prints the right frame (it is the same as the one of the selected cell). However, mimicView position isn't exactly on top of the cell when it is drawn. For the first cell the view's y has a small negative value. For the subsequent cells the view is drawn further and further to the left. So, for example, for the second cell, the view is drawn almost on top of the third one. And for the third one, it is drawn almost on top of the fifth one. In Interface Debugger I see that its x value is way too big and y value is a small negative number (like -20 - -40 points), however, the print statement still shows the same values as for the selected cell.
If someone knows why this is happening, I would really appreciate your help.
Make sure that you convert the cell's bounds to the view's coordinate system (the view to which you want to add the new subview) and use the resulting rectangle as your covering view's frame:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
let cellFrame = cell.convert(cell.bounds, to: view)
let coverView = UIView(frame: cellFrame)
coverView.backgroundColor = .darkGray
view.addSubview(coverView)
}
I have a collection view that the frame appears too tall on the iPhone X. On every other device, the sizing and scrolling works properly as shown below:
However on iPhone X, it looks like this:
The top row is cut off, and it does not scroll all the way down to the last row. Somehow, the sizing correctly calculates the width but not the height, which is about 70 pixels too tall. (I'm not worried about the top and bottom bars. I'll fix those later)
I'm guessing this has something to do with the inset adjustments for the iPhone X screen, but I can't figure out how to fix it. I've tried this in where I size the collection view:
if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior = .always
}
However, I can't seem to fix it.
Edit:
To clarify, the set up for this menu is as follows, there are actually two collection views onscreen. The first scrolls horizontally with paging enabled so that it locks onto each cell. The other collection views are the cells for the first one, and they scroll vertically. We'll call these subCollectionViews.
The subCollectionViews are receiving a size from the original collection view thats too tall. On the storyboard, the collection view's height is defined with respect to the top bar and the bottom paging bar as flush. In the story board, the height of the collection view is about 70 pixels larger than the calculated height during runtime.
So for the cell's layout guide:
override func awakeFromNib() {
cellImage = UIImageView(frame: contentView.frame)
cellImage?.contentMode = .scaleAspectFill
cellImage?.clipsToBounds = true
contentView.addSubview(cellImage!)
}
and for the collection view's layout:
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 8
layout.minimumInteritemSpacing = 8
for the storyboard presets:
I think this is what y'all asked for. If there's anything you need to see, just ask.
I also facing this issue. Need to called safeAreaLayoutGuide for control the size of item.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if #available(iOS 11.0, *) {
return view.safeAreaLayoutGuide.layoutFrame.size
} else {
// Fallback on earlier versions
return view.frame.size
}
}
Add below lines of code. I think it will solve your problem.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.aCollectionVew.setNeedsLayout()
self.aCollectionVew.layoutIfNeeded()
}
Please take all traits, spacing and contentInsetAdjustmentBehavior.
Happy Coding...:)
I want to draw line in between two Cell in UIcollectionView,
Below is design what exactly I want
Above design is dynamically change depends on array value.Please tell me if any one know how to draw line .
Well this is a request, as you've shown no approach so far, but here I am responding to your post with a solution which works with the collectionView
UI
Add a UICollectionView in your controller which lets say has a height of 100pt and its padding to [left, right, top] is 0. (Configurable via Autolayout)
Inside the UICollectionView add UICollectionViewCell which has 3 elements
A UIView (Or your custom UIImageView which will create your yellow line. It needs to be vertically centered, leading and trailing to superview equal to 0 and the height lets say 5pt
A UIImageView which will be centered and depending on the node, it will show start, progress or end node. It needs to be centered horizontally and vertically, and a custom height and width.
A UILabel which will show the node names (if start or end it needs to be hidden). Put a leading and trailing margin to 8pt and center it vertically.
View tree should look something like this:
Code
The idea is that each node of your route will be represented by a similar cell, and without leaving leaving spaces between UICollectionViewCells you will have a similar effect.
Note: I didn't create a custom cell, and that should be something that
you have to do it on your own, where you will be able to change the
content depending on your data. And let this be a task for you to
learn :)
class ViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
// MARK: - Configurations
private func setupCollectionView() {
// Set datasource
collectionView.dataSource = self
// Set flow layout
let layout:UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: 100, height: 100.0)
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView.collectionViewLayout = layout
}
// MARK: - Protocol Conformance
// MARK: UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"CellIdentifier", for: indexPath)
if indexPath.item == 0 {
// This is the start of the route
} else if indexPath.item == (collectionView.numberOfSections - 1) {
// This the the end of it
} else {
// Other nodes on your route
}
return cell
}
}
Output
Option 1
Create a view that has a horizontal line through the middle.
Add it as a subview to your view controller root view.
Create a collection view without a line. Make the background transparent.
Add it as a subview to your view controller root view.
Make sure both views are the same size.
Option 2
Create a custom UICollectionViewCell that is square in shape.
Position your circular icon in the middle of the cell.
Draw a yellow line through the cell.
Make the rest of the cell transparent, with no edges, so all you see is the icon and the yellow line.
Layout your square shaped cells so that there are no gaps in between.
Option 3
Create a custom UICollectionViewCell that is square in shape.
Position your circular icon in the middle of the cell.
Draw a yellow line through the cell.
Create a UICollectionReusableView subclass that will act as a decoration view. Draw the same yellow line here (you may want to make your 'yellow line drawing code' re-usable).
Create a custom layout that positions your cells side by side, with decoration views in between.
This option allows you to be more creative. Maybe you want to dynamically change the distance between each cell with an animation? Or maybe you want to increase the gap between just a single pair of cells? To do this, you would setup multiple layouts that differ slightly in their implementation (or a single layout that is configurable) and swap between instances thereof.
This is my most recent custom collection view layout implementation. It's a grid layout with section header views, so not exactly what you want, but it should get you started.
Other Refs
Create Custom Layout
Swap Between Custom Layouts
I have a UICollectionViewController using a UICollectionViewFlowLayout where my itemSize is the size of the UICollectionView. Basically, this is a line layout of cells where each cell is fullscreen and scrolls horizontally.
In my UICollectionViewFlowLayout subclass, I have overridden prepareLayout as follows:
- (void)prepareLayout {
self.itemSize = self.collectionView.frame.size;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.pagingEnabled = YES;
self.minimumLineSpacing = 0.0;
self.minimumInteritemSpacing = 0.0;
self.sectionInset = UIEdgeInsetsZero;
self.footerReferenceSize = CGSizeZero;
self.headerReferenceSize = CGSizeZero;
}
The UICollectionViewController is very basic returning 10 items in one section. I've included a sample project on GitHub for more detail.
Everything appears to be set up correctly. It looks right in the simulator and on the device but, when the collection view is displayed, there is an error logged to the console:
the behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less that the height of the UICollectionView minus the section insets top and bottom values.
Note also that the collection view controller in my example is in a navigation controller and while that doesn't look particularly necessary in the example, in my real-world case I need the collection view in a navigation controller.
There is a property on UIViewController–automaticallyAdjustsScrollViewInsets–that defaults to YES. This means that when a UIViewController has a UIScrollView in its view hierarchy–which is true of a UICollectionViewController–the contentInset property of that scroll view is adjusted automatically to account for screen areas consumed by the status bar, navigation bar, and toolbar or tab bar.
The documentation for that property states:
automaticallyAdjustsScrollViewInsets
Specifies whether or not the view controller should automatically adjust its scroll view insets.
#property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets
Discussion
Default value is YES, which allows the view controller to adjust its scroll view insets in response to the screen areas consumed by the status bar, navigation bar, and toolbar or tab bar. Set to NO if you want to manage scroll view inset adjustments yourself, such as
when there is more than one scroll view in the view hierarchy.
The solution is to set automaticallyAdjustsScrollViewInsets to NO somewhere in your UICollectionViewController subclass, such as in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = NO;
}
I have put an example project on GitHub that illustrates this problem and solution. There are two branches: with_error and fixed_error. Here is a diff of the change on GitHub.
iOS 11 update: automaticallyAdjustsScrollViewInsets is deprecated in iOS 11.0.
Apple recommends using UIScrollView's contentInsetAdjustmentBehavior method instead. I set this value to .never and the error has gone. You can also set this property in Interface Builder.
collectionView.contentInsetAdjustmentBehavior = .never
This issue just occured to me on 3x screens (namely the iPhone 6 Plus.) As it turned out, the autolayout engine did not really like infinite floating point values (such as .33333333), so my solution was to floor the return height in sizeForItemAt:indexPath:.
return CGSize(width: preferredWidth, height: floor(preferredHeight))
I encountered this problem when rotating the device from portrait to landscape, back to portrait. You want to invalidate the collectionView's layout upon device rotation and before the call to super, like so:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Causes collection view cells to be resized upon orientation change.
// Important that this is called *before* call to super in order to prevent error from being logged to console.
[self.collectionView.collectionViewLayout invalidateLayout];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
//...
}
This way had worked for me perfectly!.
I just subtracted the top and bottom insets from the view's height as said in that error.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width , height: view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom))
}
I hope it helps!
If you have collectionView inside scrollView just put .invalidateLayout method inside viewDidLayoutSubviews as shown below:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
I found out that .invalidateLayout method inside viewWillTransitionToSize doesn't change collection view bounds on orientation change in some cases.
A fix that worked for me.
collectionViewLayout.estimatedItemSize = .zero
or do it via IB:
Estimated Size: None
If you create the collection view in the IB, the Estimated Size property (estimatedItemSize) is set to Auto. The docs say it's .zero by default but it's not.
Like Stunner, I had the same problem when rotating from landscape (picture using full width) to portrait mode. His suggestion was the only one which really helped.
Attached the code with latest Swift for his ObjC example ... and as a bonus, my code to find the center cell of the collection view. Works quite nice ;-)
/**
-----------------------------------------------------------------------------------------------
viewWillTransition()
-----------------------------------------------------------------------------------------------
*/
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// find cell in the center of the screen and store it in a global variable
let center = self.view.convert((self.collectionView!.center), to: self.collectionView)
// get the indexPath for the cell in the center of the current screen
if let index = collectionView!.indexPathForItem(at: center) {
// store it in a class property
self.indexPathOfCenterCell = index
}
// force recalculation of margins, borders, cell sizes etc.
self.collectionView?.collectionViewLayout.invalidateLayout()
// inform UIKit about it
super.viewWillTransition(to: size, with: coordinator)
}
ios 10: topmost view was not connected to the view outlet
In my case I had property
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
set in my flow layout. So I had to switch it to some obviously wrong constant (but with some explicit height) to suppress this warning.
UICollectionView is such a thing-in-itself and sometimes absolutely unpredictable
I had similar issue.
After load cell which is full width and some height of screen. on some condition I changed the height of cell then I was getting the same error
to fix this
I used
func updateHeightPerRatio(with image:UIImage) {
let ratio = collectionView.bounds.width / image.size.width
constHeightCollectionView .constant = ceil(image.size.height * ratio)
collectionView.reloadData()
collectionView.performBatchUpdates({
collectionView.layoutIfNeeded()
}) { (completed) in
self.collectionView.reloadData()
self.layoutIfNeeded()
}
}
Solution is reload data then perform batchupdate with that collection view re -calculate the frames . after that reload collectionview again it will apply calculated frames to cell
And now there is no log for issue now.
Hope it is helpful
I was getting the same error when I was trying to embed a UICollectionView in a UITableView. I was generating a new UICollectionView in each UITableView cell, but I did not put any constraints on the height of that UICollectionView. So, when I put a constraint on the height, that error is gone!
In my case I have to reduce bottom inset (from 20 to 0) of cell as I have reduced 20 from height of collectionview
From
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
- return UIEdgeInsets(top: 10, left: 10, bottom: 20, right: 10)
+ return UIEdgeInsets(top: 10, left: 10, bottom: 0, right: 10)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 350, height: collectionView.bounds.size.height - 20)
return size
}