BananaNeil
BananaNeil

Reputation: 10762

Stack two UITableViews

I have a UITableView and a UITableViewController that I use in multiple screens in my application.

I recently spoke to the product team at my organization, and they have requested that for one of these screens, I prepend about 15 cells which look and function very differently.

After googling around, it seems that the only way to do this is to complicate every implemented UITableViewDelegate method with something like:

if section == 0 {
    ...
}
if section == 1 {
    ...
}

Which I don't want to do, because the other screens that show this table, do not need this logic, and suddenly reusability becomes way more complex.

Ideally, what I would like is to vertically stack two table views, such that one is attached to the bottom of the other.

Is something wrong with my architecture, or is there simply no nice way of doing this in IOS?

Upvotes: 1

Views: 46

Answers (2)

BananaNeil
BananaNeil

Reputation: 10762

@Fennelouski, thank you so much for you tips, I actually ended up going with your unrecommended option #4:

Put both table views in a container view

The primary reason I went in this direction is because in the past, when attempting to put scrollviews inside of other scrollviews, I found unexpected behavior regarding bouncing. Even if I set bounces to false on the nested scrollviews, I found that the parent scrollview would not move when the dragging started with the nested scrollview not quite at the end of it's content.

My solution ended up not being terrible (partly because my application is built with a framework that I designed, which allows for nifty eventing). I'll outline it here:

  1. Add both tables, first and second to a UIView, container controlled by a UIViewController, controller.
  2. Set the size of both tables to be the size of container.
  3. Set first.contentInset.bottom to second.contentSize.height and set second.contentInset.top equal to first.contentSize.height (this must be done to allow for flicking one table hard enough, to hit the end of the other table.. and must be done again anytime either of the tables change height)
  4. Set second.backgroundColor to be clear
  5. Define second's hittest method pointInside:withEvent: to return point.y > 0 (this is done, so the touch events on the empty space above second will translate down to first)
  6. When either table's delegate method scrollViewWillBeginDragging: is called, store which table began dragging (I used a property called last_dragged).
  7. When either table's delegate method scrollViewDidScroll: is called, position the table that was not last_dragged based on the offset of the table that was last_dragged

This is the code that I used for controller (which, yes, is written in ruby, but hopefully you'll be able to read it as sudo-code)

def load_view
  content.add_subview first, size: content.size
  content.add_subview second, size: content.size
  set_content_offsets
  listen_for_events
  set_second_y
end

def set_content_offsets
  first.content_inset_bottom = second.content_size_height
  second.content_inset_top = first.content_size_height
end

def listen_for_events
  listen_to_first
  listen_to_second
end

def listen_to_first
  first.on('refresh', method(:set_content_offsets))
  first.on('begin_drag', method(:second_dragged))
  first.on('scroll', method(:scroll))
end

def listen_to_second
  second.on('refresh', method(:set_content_offsets))
  second.on('begin_drag', method(:second_dragged))
  second.on('scroll', method(:scroll))
end

def first_dragged
  @last_dragged = :first
end

def second_dragged
  @last_dragged = :second
end

def scroll
  if @last_dragged == :first
    set_second_y
  else
    set_first_y
  end
end

def set_first_y
  first.content_offset_y =
    first.content_size_height +
    second.content_offset_y
end

def set_second_y
  second.content_offset_y =
    first.content_offset_y -
    first.content_size_height
end

Upvotes: 0

Fennelouski
Fennelouski

Reputation: 2431

I've had to do something similar a couple of different times and here's what I was left with:

  1. Add special cases based on section. Surprisingly, I found this to be the easiest to read and maintain. Just use static and clear variable names instead of if (section == 0).

  2. Create a special cell for row 0 that contains a table view. This isn't the easiest thing to do, but it can work nicely for being able to break your code up into multiple files which generally works better for maintenance. This does become a pain when you're sorting delegate methods, but once that is worked out then it is a very dynamic solution.

  3. If you're not using headers, then do the same as #2 but use a header for your special case. This works well (if available) when the spec changes because you can just put the view in a different header or footer.

  4. Put both table views in a container view of some sort. I tried this, it wasn't pretty. I don't recommend.

  5. Add both tables into another table. This can be done to divvy up where the cells come from. Doing this, you can subclass UITableView and create a flag to add the special table to the table. This isn't so much a solution as both a recommendation and a starting point.

Whichever option you decide, make sure it's legible. UITableView can get complicated when you're not heads down in it.

Upvotes: 3

Related Questions