Prevent jumping of UICollectionViewCells - ios

I have a UICollectionView and each cell inside it represents a message in a chat-like view. I set the top content offset everytime a new cell is added so that the cells "stick" to the bottom of the UICollectionView and new ones push the cells up the screen like this:
------------
|-----
| // This space represents a content offset from the top
|-----
A
B
C
D // Each of these is a UICollectionViewCell
------------
This works fine. However, after 8 seconds I remove the UICollectionViewCell at the top of the UICollectionView. This makes the remaining cells move up to fill its place and then I set the content offset to accommodate for the lost cell and they all move back down to act as if they're stuck to the bottom of the UICollectionView. How can I make it so that the removal of the top UICollectionViewCell doesn't make the rest of the cells jump up and down like this?

Don't use contentOffset property to manipulate the top offset. In UICollectionView all the properties like contentOffset, contentSize, etc. are managed by UICollectionViewLayout, which can be custom to match your specific needs. As for me, there are two options (by priority):
1) Look for a custom layout (or implement one by yourself) which would let you achieve the desired chat-like behavior
2) Insert a dummy empty cell at the top of your collectionView and every time you delete/add any new cells, manage the dummy one's height to achieve the goal by invalidating its layout or just calling collectionView.reloadItems([IndexPath(row: 0, section: 0)])

I solved this by removing the need for contentOffset and using the following steps:
Perform 180 degree rotation of UICollectionView: collectionView.transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: 0)
Apply the transform on the contentView of each cell in the collectionView: cell.contentView.transform = collectionView.transform
Reverse data array used in collectionView: myData.reversed()

Related

UICollectionView ScrollToItem With Insets

I have a horizontally scrolling uicollectionview with insets I am setting through the uicollectionviewdelegateflowlayout method insetForSectionAt.
In trying to use the method .scrollToItem the scroll doesn't seem to take into account of the inset and only scrolls a part of the way.
Is there a suggested approach to account for the inset and have the scroll go all the way? It is just one section and I am trying to scroll from item 0 to item 1.
Another approach is to manually modify content offset based on a custom calculation.
let customPoint = CGPoint(x: customX, y: customY)
collectionView.setContentOffset(customPoint, animated: true)
Where customX and customY are calculated based on cell size and cell count + other factors such as insetForSection.

Best way to add multiple diagonal connection lines between TableViewCells

I'm creating an app that needs to show a tableview like below image
Similar colored circles are to be matched with a line.
Which view i can add the lines?
Or need to create a new view above tableview? But still my tableview needs to be scrolled.
How can i achieve this?
Update for Bounty
I want to implement the same with incliend lines between neighbouring circles. How to achieve the same?
Demonstration below:
create design like this
Based on your requirement just hide upper line and lower line of circle
You need to create collection view in tableview cell. In collection view you create one cell. Design the same user interface like your design. Show and hide the view with matching of rule. It will not affect tableview scrolling. and with this approach you can also provide scroll in collection view cell. i can provide you coded solution if you able to provide me more information. Thanks
You can use this Third Party LIb
You need to use a combination of collection view and a table view to give support for all devices.
1.Create one collection view cell with following layout
Hide upper and lower lines as per your need
Add collection view in table view cell and managed a number of cells in collection view depending upon the current device width and item's in between spacing.
You can create a vertical label without text, set the background color with black and place it behind the circle in view hierarchy and set a width of the label as per your requirement. Then you can hide unhide the label whenever you want.
P.S.: Make sure to hide your cell separator.
I have created a demo project. You can find it here. I tried to match your requirements. You can update the collection view settings to handle the hide and show of labels.
Let me know if you have any questions.
Hope this help.
Thanks!
To connect any circle with any other circle in the cell above / below, it will be easier and cleaner to create the connection lines dynamically rather than building them into the asset as before. This part is simple. The question now is where to add them.
You could have the connection lines between every two cells be contained in the top or bottom cell of each pair, since views can show content beyond their bounds.
There's a problem with this though, regardless of which cell contains the lines. For example, if the top cell contains them, then as soon as it is scrolled up off screen, the lines will disappear when didEndDisplayingCell is called, even though the bottom cell is still completely on screen. And then scrolling slightly such that cellForRow is called, the lines will suddenly appear again.
If you want to avoid that problem, then here is one approach:
One Approach
Give your table view and cells a clear background color, and have another table view underneath to display a new cell which will contain the connection lines.
So you now have a background TVC, with a back cell, and a foreground TVC with a fore cell. You add these TVC's as children in a parent view controller (of which you can set whatever background color you like), disable user interaction on the background TVC, and peg the background TVC's content offset to the foreground TVC's content offset in an observation block, so they will stay in sync when scrolling. I've done this before; it works well. Use the same row height, and give the background TVC a top inset of half the row height.
We can make the connection lines in the back cell hug the top and bottom edges of the cell. This way circles will be connected at their centre.
Perhaps define a method in your model that calculates what connections there are, and returns them, making that a model concern.
extension Array where Element == MyModel {
/**
A connection is a (Int, Int).
(0, 0) means the 0th circle in element i is connected to the 0th circle in element j
For each pair of elements i, j, there is an array of such connections, called a mesh.
Returns n - 1 meshes.
*/
func getMeshes() -> [[(Int, Int)]] {
// Your code here
}
}
Then in your parent VC, do something like this:
class Parent_VC: UIViewController {
var observation: NSKeyValueObservation!
var b: Background_TVC!
override func viewDidLoad() {
super.viewDidLoad()
let b = Background_TVC(model.getMeshes())
let f = Foreground_TVC(model)
for each in [b, f] {
self.addChild(each)
each.view.frame = self.view.bounds
self.view.addSubview(each.view)
each.didMove(toParent: self)
}
let insets = UIEdgeInsets(top: b.tableView.rowHeight / 2, left: 0, bottom: 0, right: 0)
b.tableView.isUserInteractionEnabled = false
b.tableView.contentInset = insets
self.b = b
self.observation = f.tableView.observe(\.contentOffset, options: [.new]) { (_, change) in
let y = change.newValue!.y
self.b.tableView.contentOffset.y = y // + or - half the row height
}
}
}
Then of course there's your drawing code. You could make it a method of your back cell class (a custom cell), which will take in a mesh data structure and then draw the lines that represent it. Something like this:
class Back_Cell: UITableViewCell {
/**
Returns an image with all the connection lines drawn for the given mesh.
*/
func createMeshImage(for mesh: [(Int, Int)]) -> UIImage {
let canvasSize = self.contentView.bounds.size
// Create a new canvas
UIGraphicsBeginImageContextWithOptions(canvasSize, false, 0)
// Grab that canvas
let canvas = UIGraphicsGetCurrentContext()!
let spacing: CGFloat = 10.0 // whatever the spacing between your circles is
// Draw the lines
for each in mesh {
canvas.move(to: CGPoint(x: CGFloat(each.0) * spacing, y: 0))
canvas.addLine(to: CGPoint(x: CGFloat(each.1) * spacing, y: self.contentView.bounds.height))
}
canvas.setStrokeColor(UIColor.black.cgColor)
canvas.setLineWidth(3)
canvas.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
You'd probably want to create a Mesh class and store the images in that model, to avoid redrawing.

Different background and collectionCell separator color in UICollectionViewController

I have to create a UI which has 3 UICollectionViewCell in a row. So i created it using UICollectionViewController, with section = 1 and no of rows = coming from api. So i was able to create 6 cells in which every row contains 3 cells. These cells are separated by 0.5 pt vertically and horizontally.
I have given the background color to collectionView which comes in separator.
In particular this is what i need :-
Problem :-
This is the type of UICollectionViewController requirement i have
The separators needs to be red, and the space not rendered needs to be yellow. But I am unable to do that, I can have a single background color for UICollectionView which shows for both separator and free space. Is there a way to have different colors.
I dont want to use UITableView and have a UICollectionView inside UITableViewCell. I know this approach but i want to do the same in UICollectionViewController.
If I understand your question right, you can solve this by removing the space between the cells. Remove the 0.5pt space entirely. Then, all cells will be glued to each other, and you can create your own separators inside each cell. It'll require some logic though, because all cells should not use the same separators (if you do, then the center cells will have double separators on each side).
To achieve your photo, you could set these rules:
The first and the last cell in each row should only show a separator on bottom.
All the other cells in between (in your case, one cell in between) should show separator on bottom, left, and right.
To add these separators, just go into your custom UICollectionViewCell and add some UIViews to each side, and set them to have a width of 0.5pt, and set them visible on demand.
I would suggest a custom cell as Sti suggested. I usually do that. but an easier way would be:
To add a subview to the collectionView every time you reload data of
the collection view.
Code:
let count: CGFloat = CGFloat(items.count)
let numberOfRows: CGFloat = ceil(count / 3.0)
let height: CGFloat = numberOfRows * (cellHeight + seperatorHeight)
if collectionBackView != nil { // controller member object
collectionBackView.removeFromSuperview()
}
collectionBackView = UIView(frame:
CGRect(x: 0, y: 0, width: collectionView.frame.size.width, height: height))
collectionView.addSubview(collectionBackView)
collectionView.sendSubview(toBack: collectionBackView)

How to I build a continuous parallax scroll/Collection view?

I am looking to build a continuous parallax scroll like this.
http://www.screencast.com/t/7Z48zkkW
But my Requirement is view and then CollectionView like this
When collection view scroll then changes upper view height.
and yes upper view and collection view is separate.
I Have also navigation bar it also goes up when user scroll
You can use collectionView with section header for this effect. However, the effect collectionView bounced below the search can't be achieved by this way instead the whole view will bounce.
Simplest solution for iOS 9 +
1) Make a cell for items above the search
2) Make reusableView for search bar (section header)
3) Make cells for those pictures
i.e. you will have two cell's prototypes and one reusableView
In viewDidLoad of viewController with collectionView use following code so that header of section gets pinned always in top:
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true
Number of section: Keep it 2
Number of row for section == 0 will be 1 which will be items above search
Number of row for section == 2 will be number of images you have
Now, in collection view datasource you have a method viewForSupplementaryElementOfKind which you need to implement to get sectionHeaderView.
implement referenceSizeForHeaderInSection function for hiding sectionHeader for first section (section == 0) by returning CGSize(width: 0, height: 0) and for second section (section == 1) return size of searchBar as you want.
in cellForItemAtIndexPath return cells according to section.
EDIT
I think you don't need sticky header at top like that search bar. So its better you use collectionView with section header. (section header contains the red View you marked)

How to adjust UICollectionView contentSize.height based on number of rows in swift

I'd really appreciate if someone can light up some ideas here. I've been trying to fix this for weeks now (not kidding).
I have a "to do list" using a collectionView where I hide the rows that were completed and move them to the end of the list. I then unhide the items if needed with a button. The collectionView looks exactly as a tableView with one item(cell) per row.
When the items are hidden the collectionView has a lot of empty scrolling space at the bottom instead of automatically deleting the space used by the hidden rows since they technically are still there.
I'm trying to cut that empty space so the collectionView height would be equal to the amount of cells/rows left visible.
override func viewDidAppear(animated: Bool) {
if isCellHidden { //checking if cells are hidden
var heightOfSection0 = 95 + (42 * self.collectionView.numberOfItemsInSection(0))
println(self.collectionView.contentSize.heigh) //returns 2350
self.collectionView.contentSize.height = CGFloat(heightOfSection0)
println(heightOfSection0) //returns 1019
println(self.collectionView.contentSize.heigh) //returns 1019.0 which is correct but as soon as I scroll down it resets to it's original size (2350) and let's me scroll through that empty space...
}}
If I try to read the collectionView height immediately after setting this, it displays the correct value but as soon as I try to scroll down it resets back to it's original height. I also tried disabling the auto layout and it doesn't make a difference
You should not manage contentSize directly - return appropriate number of items to be displayed from collectionView(_:numberOfItemsInSection:) instead (i.e. do not count your "hidden" cells).
You can use sizeForItemAtIndexPath: to change the size of collection view cell.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var numberOfCellInRow : Int = 3
var padding : Int = 5
var collectionCellWidth : CGFloat = (self.view.frame.size.width/CGFloat(numberOfCellInRow)) - CGFloat(padding)
return CGSize(width: collectionCellWidth , height: collectionCellWidth)
}
Changing UI stuff in viewDidAppear() in theory is a good place to do so. However, at this point, you can't be sure that auto layout has operated on the subview that you're manipulating.
You can check when it has and when it's safe to manipulate the frame, by subclassing it and overriding layoutSubviews()
I had a similar issue here: https://stackoverflow.com/a/30446125/4396258

Resources