Collection Views

Collection Views are very similar to Table Views. You will need to follow the same logic to create a new collection, as you would do to create a new table view:

One main difference between collections and tables is the layout. If tables all look the same and have a vertical layout, collections view's layout can be customized to whatever you need.

Since this is a beginner's Rubymotion tutorial, we will be using the easiest way to lay the data out: horizontal layout.

Unfortunately, collection view initialization is a little bit different - when creating new collection view, we need to specify it's layout properties. Therefore, code won't be as pretty as with a plain table view:

        class CollectionScreenLayout < MotionKit::Layout
          def layout
            # create a collection view layout object:
            flow_layout = UICollectionViewFlowLayout.alloc.init

            # set layout flow direction:
            flow_layout.scrollDirection = UICollectionViewScrollDirectionHorizontal

            # create a collection view with a given layout:
            collection = UICollectionView.alloc.initWithFrame CGRectZero,
              collectionViewLayout: flow_layout

            # add collection view to our layout:
            add collection, :collection
          end

          def collection_style
            background_color 0xffffff.uicolor
            constraints do
              x 0
              y 0
              right.equals(:superview, :right)
              bottom.equals(:superview, :bottom)
            end
          end
        end
      
Collection View Layout here is an object that can help you to customize collection view layout. You can make it horizontal or vertical; you can set minimum space between each item or line, etc.

By default, when we write add UILabel, :title, motion-kit assumes that this element can be created with a usual UILabel.new or UILabel.alloc.init. However, UICollectionViews (and grouped UITableViews) need some additional params during initialization. In those cases, we have to create an element first, and the add it to the layout, just like we did in this example.

So we have created layout object and used it to initialize a full-screen collection view. Let's now create a cell's layout. To keep it simple, let's just add one label there, somewhere in the center of the cell. Collection Cell Layout code will not have anything new:

        class CollectionCellLayout < MotionKit::Layout
          def layout
            # we will be adding our layout to a view that will be
            # passed as a `root` parameter when creating a cell
            root :cell do
              add UILabel, :title
            end
          end

          def cell_style
            # we want all our cells to have a light gray background.
            # `cell` in the method name is the name of the root view. See
            # that `root :cell do` in the `layout` method? This is it.

            background_color 0xfafafa.uicolor
          end

          def title_style
            font          "Helvetica".uifont(16)
            text_color    0x000000.uicolor
            numberOfLines 1

            constraints do
              center_y.equals(:superview, :center_y)
              left.equals(:superview, :left).plus(15)
            end
          end
        end
      

We have out layouts ready, now we need to create a cell. All CollectionView cells should be based on a UICollectionViewCell:

        class CollectionCell < UICollectionViewCell
          def initWithFrame(frame)
            super

            # this is where we tell motion-kit, that layout should
            # be rendered inside self.contentView. This view
            # will be accessible inside layout code as a `cell`. Remember that
            # `root :cell do` code there?
            @layout = CollectionCellLayout.new(root: self.contentView).build
            @title = @layout.get(:title)

            self
          end

          def title=(text)
            @title.text = text
          end
        end
      

This is it! Now we can start to configure the collection view inside the screen. Let's create CollectionScreen as usual: set its layout in load_view method, and then let's start to configure collection in on_load:

        class CollectionScreen < PM::Screen
          title "Collection"

          def load_view
            @layout = CollectionScreenLayout.new
            self.view = @layout.view
          end

          def on_load
            self.view.backgroundColor = 0xffffff.uicolor

            collection = @layout.get(:collection)
            collection.dataSource = self
            collection.delegate = self
            collection.registerClass(CollectionCell, forCellWithReuseIdentifier: "reuse_id")

            set_data
          end

          def set_data
            @data = [
              {
                # generate 20 cells with a pattern: Cell 1, Cell 2, etc:
                cells: (1..20).to_a.map { |i| { title: "Cell #{i}"} }
              }
            ]
          end
        end
      
First we got collection view from the layout by its name. Then we have specified DataSource and Delegate just like in Table Views. This means that when our collection needs some additional info, it will call delegate methods in a current ( self ) object. Then we also told collection view to use CollectionCell cells and set our data. Collection View data is very simple in this example: just one section with 20 automatically generated cells.

(1..20).to_a.map { |i| { title: "Cell #{i}"} } may look tricky, but it is actually very easy. You can try it in irb:

As usual, we need to set Collection View delegate and data source methods. They are very similar to those that you have used with table views:

        def numberOfSectionsInCollectionView(collectionView)
          @data.count
        end

        def collectionView(collectionView, numberOfItemsInSection: section)
          @data[section][:cells].count
        end

        def collectionView(collectionView, cellForItemAtIndexPath: index)
          cell = collectionView.dequeueReusableCellWithReuseIdentifier "reuse_id",
            forIndexPath: index
          item = @data[index.section][:cells][index.row]

          cell.title = item[:title]

          cell
        end

        def collectionView(collection, layout: layout, sizeForItemAtIndexPath: index)
          [200, 150]
        end
      
The code is just like we used in our previous table views; the only difference is a collectionView(collection, layout: layout, sizeForItemAtIndexPath: index) method. It is used to specify our cell's size, for example, 200x150. Try to remove this method, or try to play with its values.

There is only one thing left - we need to open this CollectionScreen somehow. To do it, we need to go back to the HomeScreen, and tell its table to open CollectionScreen each time user taps on a first cell. To do it, we will use Table View Delegate's method tableView(table, didSelectRowAtIndexPath: index), where we will check tapped cell's number, and if it is 0 - than we will open CollectionScreen:

        def tableView(table, didSelectRowAtIndexPath: index)
          if index.row == 0
            open CollectionScreen.new
          end
        end
      
Now we can launch the app with rake, and go to Collection View screen:

You can try to scroll colelction view horizontally, and you will find all other 14 hidden cells. Let's play with Collection View delegate methods a bit: each time user taps on a cell, we will show him an alert view that will tell him which cell has been tapped.

To do it, we will use default iOS AlertView. Without sugarcube gem, code to show Alert View would look like this:

        item = @data[index.section][:cells][index.row]
        alert = UIAlertView.alloc.initWithTitle "#{item[:title]} tapped!",
          message: nil,
          delegate: self,
          cancelButtonTitle: "OK",
          otherButtonTitles: nil
        alert.show
      
However in Rubymotion we can use sugarcube, that allows us to show same Alert View just like this:
        item = @data[index.section][:cells][index.row]
        UIAlertView.alert "#{item[:title]} tapped!"
      
Let's put this inside Collection View delegate's method collectionView(collectionView, didSelectItemAtIndexPath: index) and try to run the app:
You can figure out that item in the code above is a cell's hash in our @data. We've got it by getting the section of a tapped cell: @data[index.section], and then we tried to get it's cell, which index is saved in index.row: @data[index.section][:cells][index.row].

This is a good time to make a commit:

        git add .
        git commit -m "Collection View added"
      

Summary

In this chapter, we learned how to create collection views. Process of their implementation is very similar to UITableView:

If you need more info on Collection Views, just visit the Apple Developer Portal. UICollectionView Delegate and DataSource can be found there as well.

Book Index | Next