iOS - CollectionView rotation responds slow - ios

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)
}

Related

CollectionView FlowLayout overlapping when scrolling not working

I hope you all working fine
Here I have created a collection view flow layouts some scenarios not working, please share your references.
here is the collection view
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var collection: UICollectionView!
private let kCellHeight: CGFloat = UIScreen.main.bounds.width/2+38
private let kItemSpace: CGFloat = UIScreen.main.bounds.width/2.08
lazy var expand_click = false
lazy var index_value = [String]()
override func viewDidLoad() {
super.viewDidLoad()
collection.delegate = self
collection.dataSource = self
let layout = StickyCollectionViewFlowLayout2()
layout.minimumLineSpacing = -kItemSpace
collection.collectionViewLayout = layout
collection.reloadData()
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 15
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
print("Index",indexPath.row)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width-40, height: kCellHeight)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (15)-1 == indexPath.row {
self.showAlert(alertText: "", alertMessage: "last Cell")
}else{
if index_value.contains("\(indexPath.row)") {
self.showAlert(alertText: "", alertMessage: "Redirect")
expand_click = true
}else{
index_value.removeAll()
index_value.append("\(indexPath.row)")
expand_click = true
cellData = indexPath.row
collectionView.performBatchUpdates({
}, completion: nil)
}
}
}
}
collection view cells
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var container: UIView!
override func layoutSubviews() {
container.backgroundColor = .white
container.layer.cornerRadius = 13
container.layer.shadowRadius = 2
container.layer.shadowOpacity = 0.7
container.layer.shadowOffset = CGSize(width: 0, height: 1)
container.layer.shadowColor = UIColor.black.withAlphaComponent(0.45).cgColor
}
}
When clicked cell it will be expanding the cell and moved to be under the other the cells
> * Expanded cell again click it will be redirected to another page forEx (I showed alert)
> * the last cell click it's should also redirect These are all I have done my self if we have 15 cards it working fine more than 50 cards
> not smooth scrolling and we have a problem for contending size maybe I
> could not solve the issue
**Collection view FlowLayout**
class StickyCollectionViewFlowLayout2: UICollectionViewFlowLayout {
var firstItemTransform: CGFloat?
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let items = NSArray (array: super.layoutAttributesForElements(in: rect)!, copyItems: true)
print("------------------------------------------------------------------------------------------------------------------------")
print("Count-------->",items.count,"<--------Count")
print("------------------------------------------------------------------------------------------------------------------------")
var headerAttributes: UICollectionViewLayoutAttributes?
self.firstItemTransform = nil
if cellData != nil {
let min = cellData!
print(min)
var b = [UICollectionViewLayoutAttributes?]()
var a = false
items.forEach({ (object) in
let object = object as! UICollectionViewLayoutAttributes
if min == object.indexPath.row {
a = true
}else{
if a == true {
b.append(object)
}
}
})
for attributes in b {
if attributes?.representedElementKind == UICollectionView.elementKindSectionHeader {
headerAttributes = attributes
}
else {
self.atributeLayout(attributes!, headerAttributes: headerAttributes)
}
}
cellData = nil
}else{
items.enumerateObjects(using: { (object, idex, stop) -> Void in
let attributes = object as! UICollectionViewLayoutAttributes
if attributes.representedElementKind == UICollectionView.elementKindSectionHeader {
headerAttributes = attributes
}
else {
self.atributeLayoutReset(attributes, headerAttributes: headerAttributes)
}
})
}
return items as? [UICollectionViewLayoutAttributes]
}
func atributeLayout(_ attributes: UICollectionViewLayoutAttributes, headerAttributes: UICollectionViewLayoutAttributes?){
let minY = self.collectionView!.bounds.minY + self.collectionView!.contentInset.top
var maxY = attributes.frame.origin.y+attributes.frame.height/2+80
if let headerAttributes = headerAttributes {
maxY -= headerAttributes.bounds.height
}
let finalY = max(minY, maxY)
var origin = attributes.frame.origin
let deltaY = (finalY - origin.y) / attributes.frame.height + 100
if let itemTransform = self.firstItemTransform {
let scale = 1 - deltaY * itemTransform
attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
}
origin.y = finalY
attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
attributes.zIndex = attributes.indexPath.row
}
func atributeLayoutReset(_ attributes: UICollectionViewLayoutAttributes, headerAttributes: UICollectionViewLayoutAttributes?){
cellIndex = nil
if isExpandCell != true {
let minY = self.collectionView!.bounds.minY + self.collectionView!.contentInset.top
var maxY = attributes.frame.origin.y
if let headerAttributes = headerAttributes {
maxY -= headerAttributes.bounds.height
}
let finalY = max(minY, maxY)
var origin = attributes.frame.origin
let deltaY = (finalY - origin.y) / attributes.frame.height + 100
if let itemTransform = self.firstItemTransform {
let scale = 1 - deltaY * itemTransform
attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
}
origin.y = finalY
attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
attributes.zIndex = attributes.indexPath.row
// })
}else{
print(collectionView!.contentInset.top,collectionView!.bounds.minY)
let minY = 0.0 + collectionView!.contentInset.top
var maxY = attributes.frame.origin.y
if let headerAttributes = headerAttributes {
maxY -= headerAttributes.bounds.height
}
let finalY = max(minY, maxY)
var origin = attributes.frame.origin
let deltaY = (finalY - origin.y) / attributes.frame.height + 100
if let itemTransform = firstItemTransform {
let scale = 1 - deltaY * itemTransform
attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
}
origin.y = finalY
attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
attributes.zIndex = attributes.indexPath.row
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return false
}
}
if anyone experienced collection view please share me your references
Thank you
Hello please try my code. I have tested on an iPhone simulator and it works great.
import SwiftUI
struct CardView: View {
static let CARD_HEIGHT: CGFloat = 200
static let CARD_WIDTH: CGFloat = 300
static let TAB_HEIGHT: CGFloat = 30
#State var selected: Int? = nil
let cards: Int
var body: some View {
ScrollView {
ZStack(alignment: .top) {
ForEach(0 ..< cards) { card_i in
Button {
withAnimation {
self.selected = self.selected == card_i ? nil : card_i
}
print("A card was tapped. Do something with card_i here")
} label: { self.card }
.buttonStyle(PlainButtonStyle())
.frame(width: Self.CARD_WIDTH, height: Self.CARD_HEIGHT, alignment: .top)
.offset(x: .zero, y: CGFloat(card_i) * Self.TAB_HEIGHT + ((selected ?? cards + 1) < card_i ? Self.CARD_HEIGHT - Self.TAB_HEIGHT / 2 : 0))
}
}
.frame(height: Self.CARD_HEIGHT + Self.TAB_HEIGHT * CGFloat(cards), alignment: .top)
.padding()
}
}
var card: some View {
RoundedRectangle(cornerRadius: 17, style: .continuous)
.fill(Color.gray)
.overlay(RoundedRectangle(cornerRadius: 17, style: .continuous).stroke(Color.black, lineWidth: 5))
}
}
Edit
Use it like this
let swiftUIview = CardView(cards: 10, onCardTapped: { card in
print("card number \(card) tapped")
})
let uiViewController = UIHostingController(rootView: swiftUIview)
Gif removed because outdated.

Resize font along with frame of label using pinch gesture on UILabel?

Increase or decrease font size smoothly whenever user resize label using pinch gesture on it.
Note
Without compromising quality of font
Not only transforming the scale of UILabel
With support of multiline text
Rotation gesture should work proper with pinch gesture
Reference: SnapChat or Instagram Text Editor tool
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: UIFont(name: font.fontName, size: font.pointSize)!], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: UIFont(name: font.fontName, size: font.pointSize)!], context: nil)
return ceil(boundingBox.width)
}
}
func resizeLabelToText(textLabel : UILabel)
{
let labelFont = textLabel.font
let labelString = textLabel.text
let labelWidth : CGFloat = labelString!.width(withConstrainedHeight: textLabel.frame.size.height, font: labelFont!)
let labelHeight : CGFloat = labelString!.height(withConstrainedWidth: labelWidth, font: labelFont!)
textLabel.frame = CGRect(x: textLabel.frame.origin.x, y: textLabel.frame.origin.y, width: labelWidth, height: labelHeight)
textLabel.font = labelFont
}
func pinchedRecognize(_ pinchGesture: UIPinchGestureRecognizer) {
guard pinchGesture.view != nil else {return}
if (pinchGesture.view is UILabel) {
let selectedTextLabel = pinchGesture.view as! UILabel
if pinchGesture.state == .began || pinchGesture.state == .changed {
let pinchScale = round(pinchGesture.scale * 1000) / 1000.0
if (pinchScale < 1) {
selectedTextLabel.font = selectedTextLabel.font.withSize(selectedTextLabel.font.pointSize - pinchScale)
}
else {
selectedTextLabel.font = selectedTextLabel.font.withSize(selectedTextLabel.font.pointSize + pinchScale)
}
resizeLabelToText(textLabel: selectedTextLabel)
}
}
}
I solved the problem with following code which is working fine with every aspect which are mentioned in question, similar to Snapchat and Instagram:
var pointSize: CGFloat = 0
#objc func pinchRecoginze(_ pinchGesture: UIPinchGestureRecognizer) {
guard pinchGesture.view != nil else {return}
let view = pinchGesture.view!
if (pinchGesture.view is UILabel) {
let textLabel = view as! UILabel
if pinchGesture.state == .began {
let font = textLabel.font
pointSize = font!.pointSize
pinchGesture.scale = textLabel.font!.pointSize * 0.1
}
if 1 <= pinchGesture.scale && pinchGesture.scale <= 10 {
textLabel.font = UIFont(name: textLabel.font!.fontName, size: pinchGesture.scale * 10)
resizeLabelToText(textLabel: textLabel)
}
}
}
func resizeLabelToText(textLabel : UILabel) {
let labelSize = textLabel.intrinsicContentSize
textLabel.bounds.size = labelSize
}
Call following method every time after UILabel size changes.
func labelSizeHasBeenChangedAfterPinch(_ label:UILabel, currentSize:CGSize){
let MAX = 25
let MIN = 8
let RATE = -1
for proposedFontSize in stride(from: MAX, to: MIN, by: RATE){
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attribute = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: CGFloat(proposedFontSize))]
// let context = IF NEEDED ...
let rect = NSString(string: label.text ?? "").boundingRect(with: currentSize, options: options, attributes: attribute, context: nil)
let labelSizeThatFitProposedFontSize = CGSize(width: rect.width , height: rect.height)
if (currentSize.height > labelSizeThatFitProposedFontSize.height) && (currentSize.width > labelSizeThatFitProposedFontSize.width){
DispatchQueue.main.async {
label.font = UIFont.systemFont(ofSize: CGFloat(proposedFontSize))
}
break
}
}
}
you can try:
1 - Set maximum font size for this label
2 - Set line break to Truncate Tail
3 - Set Autoshrink to Minimum font size (minimum size)

UICollection view cell with large text problem

I have a cell with description(UILabel) and two buttons after the description I use this to get the description text height
let constraintRect = CGSize(width: maxWidth, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: font], context: nil)
return ceil(boundingBox.height)
but the problem is when I do any action on the buttons with a huge description cell the actions is not trigged this is a ver annoying a problem and tried a lot of solutions but nothing works any help ?
this is where I set the height for cells
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
if self.items.count < 1 {
return CGSize(width: (self.view.bounds.width - 16), height: 200)
}
if indexPath.item < self.items.count {
let item = self.items[indexPath.item]
var h = CGFloat(0)
h = item["description"].stringValue.height(with: (self.collectionView.bounds.width - 44), font: UIFont(name: "Helvetica", size: self.contentLabelFontSize)!)
self.descriptionHeight = h
if item["images"].isEmpty == false {
if item["images"][0]["original"].stringValue != "" {
let hi = 300
let im_h = CGFloat(hi)
h += im_h
}
}
if self.type == "events" {
h += 260
}
else if item["type"].stringValue == "event" {
h += 250
}
else {
h += 170
}
let size = CGSize(width: (self.view.bounds.width - 16), height: h)
return size
}
return CGSize(width: (self.view.bounds.width - 16), height: 50)
}
And this is where is set the height of label at the PostCell class :
self.contentLabel.frame = CGRect(x: 10, y: 110, width: (self.bounds.width - 20), height: (self.bounds.height - self.imageHeight - 170))

Rotation Lanscape collectionView

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)
}

Set height for popover in landscape mode

I want to modify the height of popover in landscape mode, but it only work in portrait mode.
I want it's height equal to screenSize.height * 0.7, but it doesn't work with my code below.
Here is my code:
if let orientation = UIDevice.current.value(forKey: "orientation") as? Int {
let diamondViewController = DiamondViewController()
diamondViewController.mode = .buyDiamondPopup
diamondViewController.resetBackgroundColor = {
self.view.backgroundColor = .clear
}
let screenSize = UIScreen.main.bounds
if orientation == 3 { // LandscapeRight
diamondViewController.preferredContentSize = CGSize(width: screenSize.width * 0.6, height:
screenSize.height * 0.7)
} else {
diamondViewController.preferredContentSize = CGSize(width: screenSize.width - 60, height:
min(screenSize.height - 180, CGFloat(5 * 70 + 110) ))
}
diamondViewController.modalPresentationStyle = .popover
if let popover = diamondViewController.popoverPresentationController {
popover.permittedArrowDirections = .init(rawValue: 0)
popover.sourceView = self.view
popover.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover.delegate = self
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.present(diamondViewController, animated: true, completion: nil)
}
}
...
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Instead of this
diamondViewController.preferredContentSize = CGSize(width: screenSize.width * 0.6, height: screenSize.height * 0.7)
Try this below
diamondViewController.view = CGRect(x: diamondViewController.view.frame.origin.x ,y: diamondViewController.view.frame.origin.y ,width: screenSize.width * 0.6, height: screenSize.height * 0.7)
If it doesn't help then let me know.
Have you tried using the "Vary for traits" tool? It allows you to apply different constraints depending on the devices orientation. It can be found near the bottom right corner in storyboard next to the different constraint options.
I think you have to set landscape's height in a dispatched thread. Here is my suggestion.
if let orientation = UIDevice.current.value(forKey: "orientation") as? Int {
let diamondViewController = DiamondViewController()
diamondViewController.mode = .buyDiamondPopup
diamondViewController.resetBackgroundColor = {
self.view.backgroundColor = .clear
}
// HERE IS THE CHANGE: CALL FUNC TO GET SIZE
let size = diamondViewController.myPreferedContentSize(landscape: landscape)
diamondViewController.preferredContentSize = size
...
}
}
The func myPreferedContentSize is defined in 'diamondViewController' class as following:
func myPreferedConentSize(landcape: Bool) -> CGSize
{
let screenSize = UIScreen.main.bounds
let retSize: CGSize
if landscape { // LandscapeRight
retSize = CGSize(width: screenSize.width * 0.6, height:
screenSize.height * 0.7)
} else {
retSize = CGSize(width: screenSize.width - 60, height:
min(screenSize.height - 180, CGFloat(5 * 70 + 110) ))
}
return retSize
}
In your diamondViewController class, override this func and add codes:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let landscape = size.width > size.height
DispatchQueue.main.async {
[weak self] in
guard let this = self else { return }
this.preferredContentSize = this.myPreferedConentSize(
landscape: landscape)
}
}
It is important to call func myPreferedConentSize in DispatchQueue.main.async thread. In this async main thread, it will ask your view controller to set preferred content size in either portrait or landscape orientation correctly.

Resources