Rotation Lanscape collectionView - ios

I used collection view to display 10 cells, with portraits:
and on rotation:
I just try code
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let followLayout = demoView.collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return }
followLayout.itemSize = UIScreen.main.bounds.size
followLayout.invalidateLayout()
view.setNeedsLayout()
}
but does not affect.
I just try
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard let followLayout = detailView.collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
followLayout.invalidateLayout()
let offset = detailView.collectionView.contentOffset
let width = detailView.collectionView.bounds.size.width
let index = round(offset.x / width)
let newOffset = CGPoint(x: index * size.width, y: offset.y)
detailView.collectionView.setContentOffset(newOffset, animated: false)
coordinator.animate(alongsideTransition: { (context) in
// self.demoView.collectionView.reloadData()
self.detailView.collectionView.setContentOffset(newOffset, animated: false)
}, completion: nil)
}
and this work for me. Thanks for all

I suggest you to center align for every cell
Swift 4
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let totalCellWidth = CellWidth * CellCount
let totalSpacingWidth = CellSpacing * (CellCount - 1)
let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}

Related

How to center each view in the collection with UICollectionViewFlowLayout?

Similar questions are posted here, but the problem relies on my code and I don't know to solve it.
I don't know why, but as I'm scrolling my collectionview, the cells are moving more to the left. See image below:
Here is the code for my UICollectionViewFlowLayout
import UIKit
class PrayerFlowLayout: UICollectionViewFlowLayout {
//let standardItemAlpha: CGFloat = 0.3
let standardItemScale: CGFloat = 0.85
var isSetup = false
override func prepare() {
super.prepare()
if isSetup == false {
setupCollectionView()
isSetup = true
}
}
override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
var attributesCopy = [UICollectionViewLayoutAttributes]()
for itemAttributes in attributes! {
let itemAttributesCopy = itemAttributes.copy() as! UICollectionViewLayoutAttributes
changeLayoutAttributes(itemAttributesCopy)
attributesCopy.append(itemAttributesCopy)
}
return attributesCopy
}
// indicates the point on where to stop scrolling each prayer
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// get layout attribute to use to make some calculations
let layoutAttributes = self.layoutAttributesForElements(in: collectionView!.bounds)
// get the horizontal center on the collection
let center = collectionView!.frame.size.width / 2
// add the center to the proposed content offset
let proporsedContentOffsetCenterOrigin = proposedContentOffset.x + center
let closest = layoutAttributes!.sorted {
abs($0.center.x - proporsedContentOffsetCenterOrigin) < abs($1.center.x - proporsedContentOffsetCenterOrigin)
}.first ?? UICollectionViewLayoutAttributes()
let targetContentOffset = CGPoint(x: floor(closest.center.x - center), y: proposedContentOffset.y - center)
return targetContentOffset
}
func changeLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) {
let collectionCenter = collectionView!.bounds.width / 2
let offset = collectionView!.contentOffset.x
let normalizedCenter = attributes.center.x - offset
let maxDistance = collectionView!.bounds.size.width + self.minimumLineSpacing
//collectionView!.frame.width + self.minimumLineSpacing // self.itemSize.width + self.minimumLineSpacing
let distance = min(abs(collectionCenter - normalizedCenter), maxDistance)
let ratio = (maxDistance - distance)/maxDistance
//let alpha = ratio * (1 - self.standardItemAlpha) + self.standardItemAlpha
let scale = ratio * (1 - self.standardItemScale) + self.standardItemScale
//attributes.alpha = alpha
attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
}
func setupCollectionView() {
self.collectionView!.decelerationRate = UIScrollView.DecelerationRate.fast
let collectionSize = collectionView!.bounds.size
let yInset = (collectionSize.height - self.itemSize.height) / 3
let xInset = (collectionSize.width - self.itemSize.width) / 2
let topPos = (collectionView!.bounds.height - (collectionView!.bounds.height - 75) )
self.sectionInset = UIEdgeInsets.init(top: topPos, left: xInset, bottom: yInset, right: xInset)
}
}
Any ideas on how I can have all the cells always centered?
You need to use UICollectionViewDelegateFlowLayout method like this
class CardListViewController:UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
// UICollectionViewDelegateFlowLayout Delegate method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let leftAndRightPaddings: CGFloat = 20.0
let numberOfItemsPerRow: CGFloat = 1.0
let width = (collectionView.frame.width - leftAndRightPaddings)/numberOfItemsPerRow
return CGSize(width: width, height: collectionView.frame.width)
}
}
From the Storyboard Set Section Insets Like this.
Output :

iOS - CollectionView rotation responds slow

I have a collectionView where I am using the following to manage rotation of the cells:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.async {
coordinator.animate(alongsideTransition: nil, completion: { context in
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.collectionViewLayout.finalizeCollectionViewUpdates()
})
}
}
I see that the size change on rotation is very slow ( takes ~ 3- 4 seconds ). Is there a better way to get smoother rotation transition? I have dynamic cell sizing from sizeForItemAtIndexPath, code is :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width = CGFloat()
if traitCollection.userInterfaceIdiom == .phone {
width = collectionView.frame.size.width - 2.0 * margin
}
else if traitCollection.userInterfaceIdiom == .pad {
if UIDevice.current.orientation.isLandscape {
width = floor(collectionView.frame.size.width - 14.0 * margin)
}
else {
width = floor(collectionView.frame.size.width - 6.0 * margin)
}
}
let post = self.postListForLoggedInUserFromRealm?.posts[(indexPath as NSIndexPath).item]
if let description = post?.description {
let rect = NSString(string: description).boundingRect(with: CGSize(width: view.frame.width * 0.8, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [.font: UIFont(name: "Avenir", size: CGFloat(titleFont))!], context: nil)
var knownHeight : CGFloat = 64
if(post?.imageURL != nil && post?.imageURL != "") {
guard let imageURL = NSURL(string: (post?.imageURL!)!) else { return CGSize(width: width, height: rect.height + knownHeight )}
if let imageSource = CGImageSourceCreateWithURL((imageURL as CFURL), nil) {
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {
let pixelHeight = imageProperties[kCGImagePropertyPixelHeight] as! CGFloat
let imageMaxHeight: CGFloat = (CIConstants.DeviceType.DeviceTypePhone == deviceType ) ? 400 : 700
let imageHeight = pixelHeight > imageMaxHeight ? imageMaxHeight : pixelHeight
knownHeight = knownHeight + CGFloat(imageHeight)
return CGSize(width: width, height: rect.height + knownHeight)
}
}
}
return CGSize(width: width, height: rect.height + knownHeight)
}
else if(post?.imageURL != nil && post?.imageURL != "") {
var knownHeight : CGFloat = 10 + 44 + 8 + 44 + 10
guard let imageURL = NSURL(string: (post?.imageURL!)!) else { return CGSize(width: width, height: knownHeight + 70)}
if let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, nil) {
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {
let pixelHeight = imageProperties[kCGImagePropertyPixelHeight] as! CGFloat
let imageMaxHeight: CGFloat = (CIConstants.DeviceType.DeviceTypePhone == deviceType ) ? 400 : 700
let imageHeight = imageMaxHeight > 700 ? imageMaxHeight : pixelHeight
knownHeight = knownHeight + CGFloat(imageHeight)
}
}
return CGSize(width: width, height: knownHeight)
}
return CGSize(width: 850, height: 70)
}
Any suggestions would be truly appreciated! Thanks for your time :)
EDIT: When I use this code, the delay is gone but I lose the margins ( on left and right of collectionView) in Portrait mode.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
DispatchQueue.main.async {
coordinator.animate(alongsideTransition: { context in
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.collectionViewLayout.finalizeCollectionViewUpdates()
})
}
super.viewWillTransition(to: size, with: coordinator)
}

How to set different height and width of cells in horizontal collectionview

I have a horizontal collection view and it shows 5 cells at a time and i have adjusted that through sizeForItemAt :
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (self.numberCollectionView?.bounds.size.width)!/5 - 3, height: (self.numberCollectionView?.bounds.size.width)!/5 - 3 )
}
But these 5 cells would have different width and height and there is a ratio to it. So the center item would take 40% of the whole size, the 2nd and 3rd item would take 20% of the size and the 1st and 5th would take 10% of the size. According to this ratio items size would get change on scroll. These items are all Label.
Edit:
unlike the suggested question and other questions in stackoverflow in my case there is only one row and i have custom UICollectionViewFlowLayout to handle the scrolling by cell
UICollectionViewFlowLayout to handle the scrolling by cell:
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalOffset = proposedContentOffset.x
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height)
for layoutAttributes in super.layoutAttributesForElements(in: targetRect)! {
let itemOffset = layoutAttributes.frame.origin.x
if (abs(itemOffset - horizontalOffset) < abs(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
}
Then to find the center cell of the CollectionView:
private func findCenterIndex() -> Int {
let center = self.view.convert(numberCollectionView.center, to: self.numberCollectionView)
let row = numberCollectionView!.indexPathForItem(at: center)?.row
guard let index = row else {
return 0
}
self.rating = index
return index
}
under scrollViewDidScrolltransformed cell:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
transformCell()
}
private func transformCell() {
let row = findCenterIndex()
var offset : Float = 0.0
for cell in numberCollectionView.visibleCells as! [VotingCell] {
if row == numberCollectionView.indexPath(for: cell)?.row {
offset = 1.1
cell.label.font = UIFont.boldSystemFont(ofSize: 28.0)
cell.label.textColor = UIColor.black
cell.alpha = 1.0
cell.label.textAlignment = .center
}
else if row == (numberCollectionView.indexPath(for: cell)?.row)! + 1 || row == (numberCollectionView.indexPath(for: cell)?.row)! - 1
{
offset = 0.8
cell.label.font = UIFont(name: cell.label.font.fontName, size: 26.0)
cell.label.textColor = UIColor.gray
cell.alpha = 0.8
cell.label.textAlignment = .center
}
else if row == (numberCollectionView.indexPath(for: cell)?.row)! + 2 || row == (numberCollectionView.indexPath(for: cell)?.row)! - 2 {
offset = 0.7
cell.label.font = UIFont(name: cell.label.font.fontName, size: 20.0)
cell.label.textColor = UIColor.gray
cell.alpha = 0.8
cell.label.textAlignment = .center
}
else
{
offset = 0.00
}
cell.transform = CGAffineTransform.identity
cell.transform = CGAffineTransform(scaleX: CGFloat(offset), y: CGFloat(offset))
}
}
also called transformCell() from viewDidLayoutSubviews for initial load.

Different cell sizes with an collectionView based on constraints

I've created a collectionView where i have 3 cells the first cells has to fill first row and the third should fill second row however i can't seem to figure out how to do this, since everytime i try to do in sizeForItemAt i get that the collectionView frame is (0, 0, 0, 0).
Here is what i have at the moment
class FlowLayout: UICollectionViewFlowLayout {
var numberOfCells: Int!
override init() {
super.init()
setupLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupLayout()
}
func setupLayout() {
minimumInteritemSpacing = 1
minimumLineSpacing = 1
}
override var itemSize: CGSize {
set {
}
get {
let numberOfColumns: CGFloat = 2
let itemWidth = (self.collectionView!.frame.width - (numberOfColumns - 1)) / numberOfColumns
let itemHeight = self.collectionView!.frame.height / 2
return CGSize(width: itemWidth, height: itemHeight)
}
}
}
illustration of what i want
setting up collectionView in my view
func createCollectionView() {
let layout = FlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.numberOfCells = 2
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.backgroundColor = UIColor.white
collectionView.isScrollEnabled = false
self.topWrapperView.addSubview(collectionView)
collectionView.snp.makeConstraints { (make) -> Void in
make.left.equalTo(topWrapperView).offset(0)
make.top.equalTo(topWrapperView).offset(0)
make.bottom.equalTo(topWrapperView).offset(0)
make.right.equalTo(topWrapperView).offset(0)
}
}
I had a similar situation except it was the first cell that I wanted 2 cells in first row, with 3 each row after. Used it for a main profile pic and bio in a collection view of images. I successfully implemented it using sizeForItemAtIndexPath with default UICollectionViewDelegateFlowLayout.
Here is something that should work for your situation:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let insets = collectionView.contentInset
let collectionViewWidth = collectionView.frame.width - (insets.left + insets.right + 1)
let collectionViewHeight = collectionView.frame.height - (insets.top + insets.bottom)
switch indexPath.row {
case 0, 1:
return CGSize(width: collectionViewWidth / 2, height: collectionViewHeight / 2)
default:
return CGSize(width: collectionViewWidth, height: collectionViewHeight / 2)
}
}
You can change what you divide height by to get desired height.
This is what I wrote for my situation, figured I'd post it incase it can help you or anyone else who reads it. If you want it to change with rotation, use viewWillTransition and set a bool flag with a didSet listener:
var isPortraitOrientation: Bool = true {
didSet {
if oldValue != isPortraitOrientation {
collectionView?.collectionViewLayout.invalidateLayout()
}
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
isPortraitOrientation = (size.width - size.height) <= 0 ? true : false
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let insets = collectionView.contentInset
let collectionViewWidth = collectionView.frame.width - (insets.left + insets.right + 1)
let collectionViewHeight = collectionView.frame.height - (insets.top + insets.bottom)
switch indexPath.row {
case 0:
if textViewSelected {
return isPortraitOrientation ? CGSize(width: collectionViewWidth, height: collectionViewHeight / 2) : CGSize(width: collectionViewWidth, height: collectionViewWidth / 3)
} else {
return isPortraitOrientation ? CGSize(width: collectionViewWidth / 2, height: collectionViewWidth / 2) : CGSize(width: collectionViewWidth / 2, height: collectionViewHeight / 1.5)
}
case 1:
return isPortraitOrientation ? CGSize(width: collectionViewWidth / 2, height: collectionViewWidth / 2) : CGSize(width: collectionViewWidth / 2, height: collectionViewHeight / 1.5)
default:
return isPortraitOrientation ? CGSize(width: collectionViewWidth / 3, height: collectionViewWidth / 3) : CGSize(width: collectionViewWidth / 5, height: collectionViewWidth / 5)
}
}
EDIT:
based on your additional code, instead of doing CGRect.zero for the collectionView frame use topWrapperView.bounds.

How to jump to any Cell in a UICollectionView by using a custom layout?

I have 40 cells in my horizontal UICollectionView and a button.
I can jump from cell number 5 to cell number 10 when I click on the button.
But as soon as I want to go to a further cell ( for example from 5 to 25 ),
it doesn't work, and instead it goes to 0.
code:
func setValue(value: Int, animated: Bool) {
self.value = value
if let row = find(values, value) {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
selectedCell = collectionView.cellForItemAtIndexPath(indexPath) as? StepperCell
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .CenteredHorizontally, animated: animated)
}
}
override func prepareLayout() {
let start = Int(collectionView!.bounds.size.width * 0.5) - statics.width / 2
let length = collectionView!.numberOfItemsInSection(0)
if cache.isEmpty && length > 0 {
for item in 0..<length {
let indexPath = NSIndexPath(forItem: item, inSection: 0)
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.frame = CGRect(
x: start + (statics.width + statics.padding) * indexPath.item,
y: 0,
width: statics.width,
height: statics.height
)
cache.append(attributes)
}
contentWidth = CGRectGetMaxX(cache.last!.frame) + CGFloat(start)
}
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if CGRectIntersectsRect(attributes.frame, rect) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
I think you can dispense with the row logic. After all - you are just using the passed Int. If your if let clause fails, it won't scroll.
Also, drop the call to cellForItemAtIndexPath - you do not need it (you are not using it either).
I guess that perhaps you should use NSIndexPath(forItem, inSection) rather than NSIndexPath(forRow, inSection). (Table view uses row, collectionView uses item.)
collectionView.scrollToItemAtIndexPath(
NSIndexPath(forItem: value, inSection: 0),
atScrollPosition: .CenteredHorizontally, animated: true)
After some research, and some help from a fellow coder. I came up with a simpler and more effective solution.
I use custom layout only for the snapping effect (when switching between cells), and then I let the flow layout handle the rest.
here is the code :
struct Statics {
static let width = CGFloat(50.0)
static let height = CGFloat(50.0)
static let padding = CGFloat(5.0)
}
func commonInit() {
...
// I had to set layout from the code, didn't work from the Interface Builder
let layout = HorizontalLayout()
layout.scrollDirection = .Horizontal
collectionView.collectionViewLayout = layout
collectionView.dataSource = self
}
override func layoutSubviews() {
super.layoutSubviews()
// Set item size, line spacing and insets and let the flow layout do the rest
let layout = collectionView.collectionViewLayout as! HorizontalLayout
layout.itemSize = CGSize(width: Statics.width, height: Statics.height)
layout.minimumLineSpacing = Statics.padding
let sideMargin = (collectionView.bounds.width - Statics.width) / 2.0
layout.sectionInset = UIEdgeInsetsMake(0.0, sideMargin, 0.0, sideMargin)
setValue(value, animated: true)
}
class HorizontalLayout: UICollectionViewFlowLayout {
// This the only method you need to override
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let contentOffset: CGPoint
let itemWidth = itemSize.width
let padding = minimumLineSpacing
// Calculate nearest cell from the proposed content offset
let index = round(proposedContentOffset.x / (itemWidth + padding))
let x = (itemWidth + padding) * index
contentOffset = CGPoint(x: x, y: proposedContentOffset.y)
return contentOffset
}
}

Resources