I am using UIContextMenuConfiguration for actions on a collection view cell. Everything works exactly as expected, however if my cell has (de)activated constraints from nib, it refreshes upon long press.
To demonstrate the problem, I have created a new project, with a collectionView in the storyboard. A custom cell in the collection view has a label with two constraints, a constraint pinning it to the bottom of the cell (initially active), and the other aligns it in the center (initially disabled).
Here's my code,
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.configure()
return cell
}
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration.init(identifier: nil, previewProvider: nil) { (array) -> UIMenu? in
return UIMenu(title: "Hello", image: nil, identifier: nil, options: .destructive, children: [UIAction(title: "Share", image: UIImage(systemName: "tray.and.arrow.up"), identifier: nil) { _ in
print("HelloWorld!")
}])
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 265, height: 128)
}
}
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
#IBOutlet weak var centerConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure() {
bottomConstraint.isActive = false
centerConstraint.isActive = true
}
}
The issue appears to be directly related to:
two constraints created in Storyboard which would conflict with each other
setting one constraint to Not Installed
de-activating the installed constraint and activating the not-installed constraint
When initializing UIContextMenuConfiguration with previewProvider: nil - which tells UIKit to auto-generate a preview view - the "installed" states of the constraints are reset.
One way to get around it:
Instead of marking the Center constraint "not installed" give it a Priority of 998, and give the Bottom constraint a Priority of 999 (that will prevent IB from complaining).
You can then keep your configure() func as:
func configure() {
bottomConstraint.isActive = false
centerConstraint.isActive = true
}
and the label's centerY constraint will remain active in both the cell and in the context menu's preview.
However, if you WANT the previewView to look different than the cell, your best bet (and probably a better approach to begin with) is to use a custom previewProvider.
Related
I am having UICollectionView inside a UITableViewCell and its delegate and datasource are declared there in table cell class. It works fine for first time loading but later on changes it's size when I scroll collection.
I noticed sizeForItemAt is called only while loading but not while setting the cells during scroll.
class HomeCell: UITableViewCell {
#IBOutlet weak var collHomeSongs: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCollection() {
collHomeSongs.delegate = self
collHomeSongs.dataSource = self
}
}
extension HomeCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width: 120, height: 180)
}
}
I just got it's solution this problem arises in Xcode 11+, and solution to it is set collection view automatic size to "None".
Refer: Why on Xcode 11, UICollectionViewCell changes size as soon as you scroll (I already set size in sizeForItem AtIndexPath:)?
I have been trying to implement a vertical collection view inside a horizontal collection view. I have succeeded in my venture up to great extent. But when the device is rotated from portrait to landscape mode, the vertical collection view cells do not resize. Although the cells resize properly when the device enters from landscape to portrait mode. Even the horizontal collection view cells resize properly as I have invalidated the flow layout inside the viewWillTransitionToSize function. I have implemented the same function (with other functions like numberOfItemsAtIndexPath, cellForItemAt, sizeForItemAt, , etc.) for vertical collection view too (inside a class "HorizontalCollectionViewCell" in a separate CocoaTouch file). But all in vein. All the view and subviews (including the two collection views) have been created using storyboard (not code). I have been searching for the solution for last 1 week , but haven't succeeded yet. Kindly help me find a proper solution for this problem.
Here is the code inside ViewController file:-
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var horizontalView: UIView! // Container View for horizontal Collection View.
#IBOutlet weak var horizontalCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
horizontalCollectionView.delegate = self
horizontalCollectionView.dataSource = self
menuBarCollectionView.delegate = self
menuBarCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == menuBarCollectionView {
let cell = menuBarCollectionView.dequeueReusableCell(withReuseIdentifier: "menuButtonsCell", for: indexPath) as! MenuBarCollectionViewCell
cell.menuButtonsLabel.text = menuButtonsName[indexPath.row]
return cell
}
let cell = horizontalCollectionView.dequeueReusableCell(withReuseIdentifier: "horizontalCell", for: indexPath) as! HorizontalCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == menuBarCollectionView {
let menuButtonWidthFactor = self.menuBarView.frame.width - 1
return CGSize(width: menuButtonWidthFactor / 3, height: 35)
}
return CGSize(width: horizontalView.frame.width, height: horizontalView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
if collectionView == menuBarCollectionView {
return 0.5
}
return 0
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
menuBarCollectionView.collectionViewLayout.invalidateLayout()
horizontalCollectionView.collectionViewLayout.invalidateLayout()
}
}
Here is the code inside subclass HorizontalCollectionViewCell file:-
import UIKit
class HorizontalCollectionViewCell: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var verticalCollectionView: UICollectionView! {
didSet {
self.verticalCollectionView.delegate = self
self.verticalCollectionView.dataSource = self
}
}
#IBOutlet weak var verticalView: UIView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = verticalCollectionView.dequeueReusableCell(withReuseIdentifier: "verticalCell", for: indexPath)
cell.backgroundColor = UIColor.red
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.verticalView.frame.width, height: 100)
}
func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
verticalCollectionView.collectionViewLayout.invalidateLayout()
}
}
I have a UIView(TableHeaderView) which has few labels and collection view. One of the label text is set on run time. So its height is unknown. I'm setting this view as the header for table view.
let tableHeaderView = CommentsTableHeaderView.instanceFromNib() as! CommentsTableHeaderView
commentsTable.tableHeaderView = tableHeaderView
In CommentsTableHeaderview class I have the following code.
I have set postDesc label's number of lines to 0. If the text comes in few lines, its cutting off. I want this to be dynamic and display all text.
class CommentsTableHeaderView: UIView,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
#IBOutlet weak var postDesc: UILabel!
#IBOutlet weak var profileName: UILabel!
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var imageCollectionView: UICollectionView!
#IBOutlet weak var numberOfComments: UILabel!
class func instanceFromNib() -> UIView {
return UINib(nibName: "CommentsTableHeaderView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
override func awakeFromNib() {
imageCollectionView.delegate = self
imageCollectionView.dataSource = self
imageCollectionView.register(UINib(nibName: "InnerCollectionCell", bundle: nil), forCellWithReuseIdentifier: "InnerCollectionCell")
postDesc.lineBreakMode = .byWordWrapping
postDesc.numberOfLines = 0
postDesc.text = "hi"
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("inside collection view")
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("inside cell")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InnerCollectionCell", for: indexPath) as! InnerCollectionCell
cell.cellImageView.image = UIImage(named: "Recipe.jpeg")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
print("inside size\(collectionView.frame.width)")
return CGSize(width: imageCollectionView.frame.width, height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
}
}
You need a way to resize your UILabel according to the text size. Do not put height constraints on UILabel. Rather pin its bottom to the bottom view's top.
Do that programmatic resizing after viewDidLoad() - maybe in viewWillAppear() or viewDidAppear(), before you do your first reloadData() call.
Here is a nice tutorial how this should be done - it suggests doing the resizing inside viewDidLayoutSubviews(), which should also be fine.
you need 2 collectionViewDelegate methods:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: x, height: x)
}
if you wish to have a dynamic height in referenceSizeForHeaderInSection height field, set your height according to screen size for ex, UIScreen.main.bounds.height * 0.09
I have a collection view that is aligned horizontally and vertically in the container. When a user selects the cell I want that collection view to move to the bottom of the screen, and then I want a UIView to appear right above the collection view.
Below is how my collection view is constrained.
So the way I went about this was making the bottom space constraint an outlet in my code file.
#IBOutlet var collectionViewBottomConstraint: NSLayoutConstraint!
Then when a user tapped on the cell, i ran this function here
func showWatchView(selectedPath: Int) {
UIView.animate(withDuration: 0.3, animations: {
self.collectionViewBottomConstraint.constant = 0
})
clipsCollectionView.layoutIfNeeded()
}
However, when I tried this it moved the collection view to the top of the screen instead of the bottom of the screen, and it didn't even animate it, it just went up there.
The constant needs to be set to 0
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionViewOutlet: UICollectionView!
#IBOutlet weak var bottomContraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
collectionViewOutlet.delegate = self
collectionViewOutlet.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
showWatchView(selectedPath: indexPath.item)
}
func showWatchView(selectedPath: Int) {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.bottomContraint.constant = 0
self.view.layoutIfNeeded()
}, completion: { (completed) in
// do something
})
}
}
Since you want the collectionView to be at the bottom and self.view.layoutIfNeeded() needs to be called inside the animation with a duration of your choosing, 0.3 is ok for starters.
Hi I wanted to achieve the below design using swift .Please find image below for reference.
.
The only way I can think about do it is using a UICollectionView with a scrollbar but the UICollectionView needed custom spacing padding which made the scrollbar stop in the middle of the object.
Its better to make a UICollectionView view with cell width and height the same as UICollectionView's width and height and take a UIView inside it in order to achieve custom space padding which will contain your label and text.
I just created a Sample for you.
The main idea already suggested to take a view inside the Custom Collection View cell in order to achieve custom space padding.
I have taken two IBOutlets in ViewController, a myCollectionView and a pageControl.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
extension ViewController:UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
//MARK:- CollectionView Datasource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell
return cell
}
//MARK:- CollectionView Delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width, height: collectionView.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
//MARK:- ScrollView Delegates
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = myCollectionView.frame.size.width;
let page = floor((myCollectionView.contentOffset.x - pageWidth / 2) / pageWidth) + 1
pageControl.currentPage = Int(page)
print(page)
}
}
scrollViewDidEndDecelerating will decide in which index you are and you can update the above mapView accordingly. A page number 0 indicates its the first cell (indexPath.row = 0). As you slide to second index, it will print 1.0 which means the second index.
This is my Custom Cell class of UICollectionViewCell
import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var cellView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
//To make corners round
cellView.layer.cornerRadius = 10.0
}
}
My view heirarchy
And the output
Hope you get some idea.
That bottom part looks like a UIScrollView with isPagingEnabled set to true. The 3 dots at the bottom is a UIPageControl.
Use UIScrollViewDelegate and scrollViewDidEndDecelerating to manage your other content changes.