Reordering UICollectionView in iOS7 - ios

I want to build a UICollectionView, or use something similar library to make grid view, in which i can rearrange cells. My app supports iOS 7, and, unfortunately UICollectionView method - (BOOL)beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath
is only available on iOS 9 and later.
Is there any way i can achieve the effect of 'drag and reorder' collection view cells on iOS7? Thanks.

Well, I happened to come across the very same issue, so let me add my two cents on this.
In order to achieve to get the same effect of what UIKit does when rearranging cells, I followed a more or less similar approach described below:
Add a long-press gesture recognizer into collection view so that
you’d be able to detect which cell was tapped without messing up
with collectionView:didSelectItemAtIndexPath method.
When user long-presses on a cell, take a screenshot of that cell, add the snapshot view into collection view and then hide the cell. As the user moves his finger on the screen, update the snapshot’s center to move it programmatically so it will look like the user is dragging the cell.
The trick is to decide when and how to swap cells. As the snapshot moves around, it will intersect with other cells. When it happens, you should update your data source and call moveItemAtIndexPath so it will swap the positions of original cell and the cell which the snapshot intersects.
When dragging ends, remove the snapshot from collection view and show the original cell.
You can map those steps to gesture recognizer’s states, so that we know what you should do at each state:
Began: 1
Changed: 2, 3
Ended, Cancelled, Failed: 4
I put an example project in my github account, so you can download and play around with it. Here, I will just implement the gesture callback:
Note: The below code may not work because I threw away some implementation specific details. But it should show the basic intent. Please download the full code from my github repo.
func longPressRecognized(recognizer: UILongPressGestureRecognizer) {
let location = recognizer.locationInView(collectionView)
let indexPath = collectionView.indexPathForItemAtPoint(location)
switch recognizer.state {
case .Began:
let cell = collectionView.cellForRowAtIndexPath(indexPath)
let snapshotView = cell.snapshotViewAfterScreenUpdates(true)
snapshot.center = cell.center
collectionView.addSubview(snapshotView)
cell.contentView.alpha = 0.0 // hides original cell
case .Changed:
snapshotView.center = location // will follow user's finger.
dataSource.swap(originalCellIndexPath.item, indexPath.item) // swaps data
collectionView.moveItemAtIndexPath(originalCellIndexPath, toIndexPath: indexPath) // swaps cells
originalCellIndexPath = indexPath
default:
let cell = cellForRowAtIndexPath(originalCellIndexPath)
cell.contentView.alpha = 1.0
snapshotView.removeFromSuperview()
}
}
Here is the effect you will get (recorded on iOS 8.4 Simulator):

Related

Animation in UITableViewCell breaks after cell deallocation

I have an expanding view in the cell. After i press show more button it works fine. But when i scroll down and that cell deallocates from memory and then i tap on show more button again animation breaks. How can i fix it?
Project repo on Github
Collapsed view:
Expanded view:
Debugger view, before scroll:
After cell deallocation (after scroll) breaks like this, expands behind second cell:
Based on your code and comment...
Instead of expanding your cell, you're setting clipsToBounds = false and expanding the cell's contents, allowing them to extend outside the bounds of the cell.
As you scroll a UITableView, cells are - of course - added / removed as needed. But when they are re-added, the "z-order" changes (because, with "standard" table view usage, it doesn't matter).
So, after scrolling down and then back up, and then tapping the "Show more" button, that cell may be (and usually is) at a lower z-order ... and thus its expanded "out-of-bounds" content is hidden behind the cell(s) below it.
If you want to stick with that approach (as opposed to expanding the cell itself), you can try implementing scrollViewDidScroll to make sure that cell is "on the top":
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// make sure the first row is visible (not scrolled off the top of the view)
let pth = IndexPath(row: 0, section: 0)
if let a = tableView.indexPathsForVisibleRows,
a.contains(pth),
let c = tableView.cellForRow(at: pth) {
// bring it to the front -- i.e. the top of the z-order
tableView.bringSubviewToFront(c)
}
}

Pinch gesture to expand a UICollectionView Cell

An application I'm working on requires a layout very similar to that of the Photos app on iPad. There's a grid of UIViews and the user should be able to pinch into a single one, and as they're pinching in, watch that view grow in size until it's full screen.
So far, I have set up a UICollectionViewController and a custom collection view cell. I added the pinch gesture recognizer to the custom cell. When the user pinches in, the cell grows in size.
The issue I am now having is that the cell isn't layering on top of all other cells as it expands. Rather, it is hiding under the cells loaded after it.
I was thinking that the solution may be that when the collection view controller recognizes the pinch gesture, it could hide the cell being pinched on. Then, create a new UIView that is a copy of the collection view cell, with the bounds equivalent to where the cell was placed. Then, this view can be the one that can be pinched, rotated, and panned. As soon as the user ends the gesture, the view would just animate back to the original cell position and then be set to nil.
However, I'm not sure if this is the optimal solution and maybe there's some easy way to ensure a certain cell is layered on top of all other cells in the collection view.
The code for a snippet of my current implementation of how I'm scaling the cell is shown below. This is the one that creates the issue of the cell hiding under later cells as it expands.
#objc func scalePiece(_ gestureRecognizer : UIPinchGestureRecognizer) {
guard gestureRecognizer.view != nil else { return
if gestureRecognizer.state == .began ||
gestureRecognizer.state == .changed {
gestureRecognizer.view?.transform = (gestureRecognizer.view?.transform.scaledBy(x: gestureRecognizer.scale, y: gestureRecognizer.scale))!
gestureRecognizer.scale = 1.0
}
}
One dirt-simple possibility to remedy the cell being behind others is to invoke bringSubviewToFront(_:) on the collection view, passing the cell.
Caveat however, I'm not sure that this is optimal: since you don't really own the cells/the collection view's hierarchy, generally speaking you shouldn't touch the subviews directly like this.
A closely-related possibility -- not as simple, but probably much more robust and thus preferable -- is to customize the layout. Since the collection view's UICollectionViewLayout object can specify the zIndex of a particular cell, you could reload the cell when the pinch gesture begins and update the Z index for just that cell.

Is there any way to setup gesture recognizers for repeating UI elements such as UITableViewCells?

I'm developing an iOS app with a UITableView that requires a swipe gesture to perform a certain action for any cell (row) in the table.
When I initially implemented the IBAction for the gesture in my view controller (the one with the UITableView in it), I wasn't even able to run the app, as Xcode informed me with an error that it doesn't allow attaching gestures to repeating interface elements (the cell being a repeating element, since a new one is generated each time one is needed via dequeueing a reusable cell).
So I then proceeded to place the IBAction for the swipe gesture inside my custom table cell class instead, at which point I no longer receive the error that prevents me from building/running, but am still receiving a warning in the debug console while running the app, which states that since iOS9, the system now enforces the prohibition of gestures attached to repeating elements (in this case, my table cell).
If anyone has insight into gesture recognizers, I'd appreciate it if you could help me figure out the following questions.
Should I take this warning at face value and assume that I'm not allowed to attach any gestures to table cells at all?
In either case, is there some other way to attach a gesture to repeating elements like prototype table cells and avoid any kind of warnings/prohibitions?
You should put the swipe in your viewcontroller and get a cell through Swipe IBAction with the code below. Once you've done that, you can do what you want: cell.do_something or cell.element.do_something
You get the cell with the position of swipe, like:
Objective C:
CGPoint location = [sender locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
Swift:
let location : CGPoint = sender.locationInView(self.tableView)
let indexPath : NSIndexPath = tableView.indexPathForRowAtPoint(location)
let cell : UITableViewCell = tableView(tableView, cellForRowAtIndexPath: indexPath)

Tap gesture on UICollectionView Cell using Swift

I am performing UI Automation in XCode using Swift
I would like to perform Tap gesture on UICollectionView with multiple Cells using Swift
As my Cells keeps on updating everyday. Is there anyway I can perform Tap gesture on anyone of the cell.
Try this
let app = XCUIApplication()
app.tables.cells.forEach { $0.tap() }
If you want to tap on the first cell of the collectionView, assuming there is only one collection view in the current screen
let app = XCUIApplication()
app.collectionViews.element(boundBy:0).cells.element(boundBy:0).tap()
If there are multiple collectionView, find the collection View of current interest using 'po app.collectionViews.count', then find the index of the required collectionView. Say if it is at index 'n'. Then
let app = XCUIApplication()
app.collectionViews.element(boundBy:n).cells.element(boundBy:0).tap()
If you want to tap on any cell at random, within the collection view. then you can try this
let app = XCUIApplication()
let randomCell = arc4random() % app.collectionViews.element(boundBy:n).cells.count
app.collectionViews.element(boundBy:n).cells.element(boundBy:randomCell).tap()

Stop TableView / TableViewCell animation (probably on willdisplaycell)

I've noticed this with a couple of UITableView's that I have coded, and that is when there is a lengthy list, or if I animate the displaying of the table itself, the cells have an animation, like the table is constructing the cells as I'm scrolling down.
The animation looks like the accessory view sliding from the left to the right, and I have a top-right label that does the same.
On another table that I slide in from the bottom, it looks like the buttons and text from the table expand as the table does.
How do I stop this?
I am using Swift, but if you know the answer in OBJ-C that would be okay too.
Also, if you need a preview, check this:
I've tried searching for this and I only get posts about making an animation within the table cells not removing this one!
So I found out how to stop it! It was because of the accessory view and making sure the cells have performed the layoutIfNeeded.
First, I removed the accessoryview from StoryBoard and in willDisplayCell: I used this:
UIView.performWithoutAnimation { () -> Void in
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
cell.layoutIfNeeded()
}
As well as:
UIView.performWithoutAnimation { () -> Void in
cell.layoutIfNeeded()
}
in cellForRowAtIndexPath:

Resources