Centered Paging with Preview Cells on UICollectionView

We’ve all seen it: A collection view where see parts of the next and previous cells are visible while the collection view can scroll with paging. When it scrolls, a cell is always centred.

Center

(Code on BitBucket)

This is a simple exercise but interesting because of its use of an obscure function in the UICollectionViewFlowLayout class.

This gets called when the user lifts his finger and the collection view (which is a scroll view) starts decelerating and will return the content offset where the collection view should stop. Normally, its implemented by the superclass but here we need to override it.

As a tip, there is a way to add a custom UICollectionViewLayout to a UICollectionView from Interface Builder without losing the panel provided by the standard flow layout where you can set the size of the cell and the distance between them. This is done by not setting the layout to custom as below:

Screen Shot 2015-04-23 at 12.32.03

But by changing the class on the layout outlet itself:

Screen Shot 2015-04-23 at 12.36.04

The class is shown below:

The proposed offset is where the collection view would stop without our intervention. We peek into this area by finding its centre as proposedContentOffsetCenterX and examine our currently visible cells to see which one’s centre is closer to the centre of that area.

Center2

 

Drag and Drop between UICollectionViews

(Built with XCode 6.3 – Watch the Swift version if you want it to compile!)

This took me a while to code! Extending the functionality of the drag and rearrange tutorial, we can add the ability to share data between collection views.

(Code on BitBucket)

We create 3 collection views that feed from a common root view controller which will act as both dataSource and delegate.

Screen Shot 2015-04-20 at 15.08.15

We want to make them exchange data when a cell is dragged across. The data can be anything, from a string array to a CoreData collection, but here we will create a DataItem class that will hold a colour and an id:

Notice the  Equatable interface. We will need the ‘==’ operator to compare DataItem instances. The implementation is defined outside the class through Swift’s operator overloading:

In the root view controller we create some mock data, held inside a single multidimensional array .

Here I used a convention which would normally be considered an anti-pattern but for the purpose of this example it will keep our data tidy: I assigned different tags on each collection view on the Storyboard and used these as offsets to the data array as below:

After this initial setup, we need to start a gesture recogniser. Instead of  placing it in the UICollectionViewLayout object as we did in a previous tutorial we need to abstract it into its own Manager class. To clarify things, here is a rough UML diagram:

I took the same approach as  before by creating a Bundle structure to encapsulate information about the state of the drag, as well as a canvas object where the dragging will be animated.

As we drag a cell, we need to check its relative position on the collection views and call various methods, like moveItemAtIndexPath() that will perform a swap of cells. Although most of this code could be placed in the manager class, it would prohibit the extension of the d&d to non-UICollectionViews. So we will pass this responsibility to the views themselves through the use of protocols.

When the manager sends a didMoveItem for example, a UICollectionView could decide to call the moveItemAtIndexPath while a simple UIView could rearrange its elements manually or do nothing at all.

The first thing that the manager needs to catch is the long press and decide if it is a valid point for a the start drag and drop action. It loops over the views that implement the draggable protocol, translating the press point to their coordinates and asking each one if it can start a dragging action.

Internally, each view can decide if it can do that. In our example it is a simple case of whether we get an indexPath back:

Most views will return false as the point will be outside their frame. The one that returns true is held within the bundle together with other information such as the offset to the element dragged:

DragOffset

Next on the flow of the gesture recogniser is the callback on every move of the finger or pen. It has 3 states of interest which will be caught in a case statement and described separately. On .Began we just add the snapshot and notify the view that a dragging action has started.

The collection view will then act accordingly which here means it will hide the cell being dragged so as to appear detached in the snapshot.

Here we must notice one thing. Cells move around, they get created and destroyed and there no way of knowing which one we are getting everytime we call the dequeue cell method from the collection view’s data source. This means that we cannot rely solely on hiding a specific cell but we must make sure that every time the cells get rendered we hide the cell that is currently being dragged. We do this by keeping a reference to the indexPath of that cell inside the collection view

and caching it within our root view controller’s cellForItemAtIndexPath.

As well as rendering the hidden state of a cell we also need to be able to move, insert and delete the data. The rationale is that while the collection view is responsible for all UI related tasks for the dragging and dropping, such as inserting and deleting cells in the right positions, the dataSource must be able to predictably update its mode before any visual change is made. Doing it in reverse or forgetting to update the model causes a very predictable and annoying crash! This we will achieve through a special protocol that dataSource targets of a KDDragAndDropCollectionView must implement on top of being UICollectionViewDataSource compliant.

The methods above update the model by leaving it to the controller to decide on how it is done. We can be using CoreData or plain arrays like in this example.

Let’s put everything together then.

On .Changed state of the gesture recogniser we first update the snapshot using the offset we have pre-calculated

Next we loop over our KDDroppable and calculate the intersection between their frames and the snapshot’s frame by projecting them both to the canvas for reference. We calculate the area of the intersection and keep the biggest so as to get the view that our snapshot is mostly on.

We then check to see if it is the first time we are entering into this area and if so we call the willMoveItem method from the KDDroppable protocol. We always call the didMoveItem regardless and update the overDroppableView variable so as not to call the willMoveItem again while we are on top of it:

How are these protocol methods implemented in the collection views?

The indexPathForCellOverlappingRect is a utility function that finds the cell we are mostly on just like the .Changed state did (image above). We check again as to whether the data item already exists by asking the dataSource through the extended KDDragAndDropCollectionViewDataSource protocol. If it is a new item we insert it. We do it in a performBatchUpdates block since we need to keep track of wether there are animations happening so as not to call reloadData which will cancel them.

We can see this at the last state .Ended case. In the KDDragAndDropManager class  we check if we are over a droppable view again and if yes we do the swap from one view to the other. We them remove the snapshot from the canvas.

In the stopDragging of the collection view we just nullify the draggingPathOfCellBeingDragged so that when the data gets reloaded there is not cell hidden.

KDDragAndDropCollectionView.swift

 

 

Let’s create a Calendar supporting EventKit using a UICollectionView!

(Build with XCode 6.3)

It amazes me how Apple has not provided a standard and customisable Calendar component for its UI but then again it is an opportunity to create one. While coding I also took Swift for a spin and explored lazy loading, generics and closures.

(Code on BitBucket)

Like always there are many ways of doing it but I chose the most compact which is to use a single collection view and a custom layout.

My rationale followed that of the native UICollectionView, where the view is responsible for rendering the data taken from another object that conforms to two protocols, one for data provision the other for interactivity.

Internally the calendar will cache the data taken from the delegate so that it does not make unnecessary function calls every time a section or even cell is rendered. As the calendar is multi-date selectable we need to store the indexPaths and dates that have been selected at any given point. We need to be able to access the selected data externally but set it only internally hence the private(set) directive. Note: I initialised all these variables so as not to make them optionals having to wrap them in if-let statements.

The calendar will consist of two views: a calendarView which will render the days and a headerView which will display the month and year string as well as a series of labels to indicate the days of the week. Both are created through the use of lazy loading, a feature of Swift that was previously implemented through custom getters in Objective-C.

Note: the allowsMultipleSelection variable changes the behaviour of the selection/deselection of cells massively. You can look up at Apple’s documentation for details.

The header and collection view’s frames need to be layout out with that of the calendar component’s so I override the frame using Swift’s didSet block (I saved you the details).

And now for the fun part. We want every month to be a section and every day a cell. We call the delegate objects startDate and endDate methods and cache them in the class’s local variables, we then calculate the first of the month to use as an offset where we will increment it by a month to create the calendar. This because if we where to use the start data we could fall on the case of the 30 or 31rst which would not be valid date for say the month of April. We do all that in the numberOfSectionsInCollectionView. This is a restriction that we must accept with the approach explained above (where instead of a view controller I created a view).

To render each month we basically need to know 2 things. The weekday the month starts and the number of days it has.

Using the power of Swift we use a typed dictionary called monthInfo witch will hold these 2 values.

Calendar Overview

We make the calculations on the UICollectionViewDelegate’s method which gets called for each section (month). One thing we need to notice is that the default NSCalendar has weekday 0 being a Sunday rather than a Monday. Although we could keep it like that we are going to shift the days to the more familiar Monday-indexed week.

The magic number 42 is the maximum numbers of cells that a month can need to display. It is the 7 days of the week times the 6 rows. Finally we render each cell. Since all 42 cells are going to be rendered we only need to check if the index path to be between the start and the last day of the month.

The only point of shame in the code above is the last conditional which calls the scrollViewDidEndDecelerating on the first cell. This is to give an initial notification to the delegate and set the month label inside the calendar view. If we where to use a UIViewController instead of a UIView as the KDCalendar’s superclass we might be able to do that on viewWillAppear but I stand by my choice nonetheless 😉

There is a point we need to watch for. When we created our layout above we defined our scroll direction to be:

This has a side effect that it will render cells upside down (which makes sense). In other words the second cell will be under the first rather than to the left, as in a calendar.

Direction

To fix that we need to create a custom layout and override its methods. When subclassing a UICollectionViewFlowLayout we need to account for 2 methods that get called to do the same things in different occasions. In contrast with Obj-C we can use the power of closures to write the loop in one line

The applyLayoutAttributes method will use the section and index to calculate the page and the position of the cell respectively. The base offset multiplying the section number by the size of the collection view bounds

CalendarLayout

That should give you the basic calendar.

Lastly we need a header to show an indication of the dates as well as the current month we are viewing. What is interesting is that the NSDateFormatter object has a list of the weekday and months localised so it is wise to use that. Inside the header I created a container view to place the UILabels. The format is “MO”, “TU” etc. which is created by taking the first 2 letters of the day string and capitalising it. Once again the list of days starts from Sunday so we need to loop from 1 to 7 rather than 0 to 6 and pass the value from a mod 7 so that 7 will become 0.

We need to update the month label in the header when the user scrolls to a new month as well as at the beginning (as shown in the cellForIndexPath method). This is done in the standard UIScrollViewDelegate’s callback method.

Selecting Dates

When a user presses on a cell we want to add it to a list together with the date displayed on that cell. First we check with the calendar’s delegate as to whether we can select the date.

Here I used an anti-pattern which is to save a variable (dateBeingSelectedByUser) which I just know will be used in the next function. This is because I just do not want to re-calculate the date via the indexPath offset so I trust the OS to call its methods in the proper order. If the function returns true we move on to the select/deselect methods.

The selected variable of the cell will be set automatically by the system so we only need to override it in our custom cell. In this case we only change the border width of the layer

Adding EventKit Functionality

The title said that we are going to add EventKit and that is what we are going to do. First of course we need to import it in the project

eventkit

We need to call the OS to give us the events stored in the system but first we need to ask for permission. If the user agrees we will run our function as below.

 

 

Drag and Rearrange UICollectionViews through Layouts

(Build with XCode 6.3)

We will add a much wanted functionality to our UICollectionViews. The ability to drag a cell and reposition it somewhere else in the view.

(Code on BitBucket)

To achieve this we need:

  1. Add a gesture recogniser. In this example we chose a long press recogniser which will give us a clear indication that the user wants to drag a specific cell.
  2. Set an object that knows about the collection view to be the gesture’s delegate and action target. Catch the dragging actions.
  3. Create a snapshot of the cell (UIImageView instance), hide the cell so that we only drag its representation. Add the representation on a superview called canvas.
  4. When drag over another cell swap the data (if the collection view is data driven which should be the majority of cases) and then swap the cells.
  5. When the user lets go, remove the image being dragged and unhide the cell.

Design

One decision that must be made is where to put the recogniser. This is an arbitrary choice with a few options available, but here we chose the collection view’s layout class. This will make our code more portable since we essentially need to drop a single file into a new project and simply replace a collection view’s layout to achieve the drag and rearrange functionality.

d2

We create KDRearrangeableCollectionViewFlowLayout.swift

We need to make a decision as to where the dragging action will be drawn (canvas). If a view has not been assigned explicitly we will use the collection view’s superview. This assumes that it what we want.

As we drag, we need a reference to the original cell being dragged, its representation image and its offset from the press point.

DragOffset

We also need to track the current index path so it is better to hold all that information in a separate structure we will call Bundle and will be internal to the class.

The offset it the difference between the point where the user presses and the cell’s origin. This will help maintain the same distance as we drag, rather than snapping the representation view’s origin to the press point.

UIGestureRecognizerDelegate defines the method gestureRecognizerShouldBegin giving us the chance to stop gestures that are not happening on cells. We loop over the cells and convert their frames to the canvas where we perform a hit test against the press point. After we find the correct cell we initialise a bundle instance. The existence this instance will later serve as a flag to indicate whether we are in a dragging state or not.

Dragging a Cell

Now, for the meet of the matter. The UILongPressGestureRecognizer has 3 states of interest Began, Changed and Ended. At the first we hide the cell being dragged and add its representation to the view hierarchy.

As we drag we need to update the position of the representation image, get the index path under the current point of the user’s finger (if any) and check it against the last index path saved in the bundle. If the index paths are different it is time to swap the cells!

The only unwrapping happening it at the end where the bundle needs its value updated. Also note that the code that swaps the actual data by calling moveDataItem is optional. To implement it have created a protocol defining a single method for moving data from one index path to another.

The controller in the project implements it like so:

Finally, on .Ended we need to remove the representation view and unhide the cell that is being dragged. We check as to whether we have a proper delegate one last time and we call reloadData. Otherwise there is no real need since the collection view is not data driven.

And Voilà!

Paging

There is one thing missing, paging. We need to be able to drag to the edges of our collection view and have it pan to the next page regardless of whether it is scrolling horizontally or vertically. We need to define some “hot spots” where the paging should begin.

DragEdges

We define a method checkForDraggingAtTheEdgeAndAnimatePaging that will do what it is named to! We can pre-cache the areas of collision as 4 CGRects and store them in a dictionary as can be seen in the code.  The only thing worth mentioning here is that we need to be able to pan over multiple pages. We do that by putting a timer and checking again. If the representation image is still over an area where drag is available we repeat.

Have a go and let me know!