Build a complex API-backed collection inside a table in minutes, right from Interface Builder!
Example: Messenger-like app
API URL: https://api.abstractlayer.com/demo/complex/get_contacts_and_messages
If you haven't already added the framework to your Xcode project, follow this tutorial.
-
Open
Main.storyboard
and delete the default view controller -
Drag an instance of
UITableViewController
From the menu bar choose:
Editor → Embed in → Navigation Controller
- Click on your navigation controller then check the box that says
Is initial View Controller
from the Attributes Inspector
The table view will have 2 sections (contacts & messages) each is represented by a prototype cell.
Follow the steps below to achieve the following storyboard design:
Design messages prototype cell:
-
Click on your prototype cell, and set the
cell identifier
tomessageCell
in the Attributes Inspector -
Drag the prototype cell in your storyboard from the bottom to increase its
height to 80
-
Design the table view's cell to match the design
UIImageView
for the user image (size 50x50)UILabel
for the name label (Font size 13, semibold, black)UILabel
for the last message label (Font size 12, regular, dark gray)UILabel
for the date label (Font size 12, regular, light gray)
-
Choose
2 lines
for last message label
-
To design the header of the cell, drag another UITableviewCell prototype cell from the Object Library and set its cell identifier to
messageHeader
. -
Drag a UILabel to this new header cell and rename it
Messages
Design contacts prototype cell:
-
Drag an instance of
UITableViewCell
from the Object Library and set its identifier tocontactCell
-
Drag a
UICollectionView
and make it take the full width of the cell.
Design the collection view cell:
-
UIImageView
for the user image (size 50x50) -
UILabel
for the name label (Font size 12, regular, dark gray) -
To design the header of the cell, drag another UITableviewCell prototype cell from the Object Library and place it on top of the message cell, then set its cell identifier to
contactHeader
. -
Drag a UILabel to this new header cell and rename it
Contacts
Before you move on, make sure to set the UI elements constraints properly!
It's time to bind data between the JSON document and the UI elements.
-
Open the URL in a browser https://api.abstractlayer.com/demo/complex/get_contacts_and_messages
-
Copy the URL
-
Go to your storyboard and click on your
UITableView
and change its class toALTableView
in the Identity Inspector
-
Navigate to your Attributes Inspector, and you'll find a list of new attributes
-
Paste the URL you just copied into the new
Url
field -
Each section has a different
cell identifier
, so type in both separated by a comma.contactCell,messageCell
-
Each section has its own
header identifier
, so type in both separated by a comma.contactHeader,messageHeader
-
As for the JSON root, the first section API is handled by the collection view itself, so it must be left empty, which is represented by a dash (-). This means that the first section consists of only 1 row that is not to be handled by the table parsed API itself.
-
The second section, however, has
messages
as the root of the JSON to be parsed. (Check the API to see how the response looks like), this is why you need to specify it there. The end result is-, messages
which means that the first section will be handled elsewhere, while the second section of the table will be handled by the parsed array.
Same steps apply for the collection view, so click on it, and set its class from UICollectionView
to ALCollectionView
in the Identity Inspector
- Navigate to your Attributes Inspector, and fill out the following attributes
Your table view and collection view are now ready to process the API. It's time to match the JSON keys with the UI elements to fill the data automatically.
The next section applies for both prototype cells, so make sure you apply it on both the contact and message cells.
- Click on your
UIImageView
and change its class toALImageView
in the Identity Inspector
- Navigate to your Attributes Inspector and set the following:
JSON key: Type in image_url
in the Json Key
field so that Abstract Layer can automatically load the image using its URL value
Placeholder: You can choose an image from your bundle as a placeholder image while your real image gets downloaded.
Disk Cache: By default, all images handled by Abstract Layer will be downloaded and cached both in memory and on disk. You can turn off saving on disk by choosing NO
for Disk Cache
.
Cache Policy: The default Cache Policy
is Least Recently Used (LRU).
Other available policies are (LRU, LFU, FIFO, LIFO).
Circular Option Also, to get a circular user image, turn the circular
option ON
.
Name Label
- Click on the name label and change its class to
ALLabel
in the Identity Inspector
- Type in
name
in theJson Key
field to automatically match the JSON value with the name label
Last Message Label
-
Click on the last message label and change its class to
ALLabel
in the Attributes Inspector -
Type in
last_message
in theJson Key
field
Date Label
- Click on the date label and change its class to
ALDateLabel
in the Attributes Inspector
-
Type in
timestamp
in theJson Key
field -
Type in
MM/dd/yyyy
in theOutput Format
field to get the desired date format displayed.
The format should abide by Apple's DateFormatter rules
Handle any kind of error by checking the Error handling section
Run the project, and there you go! MAGIC!
This looks great so far, but we're sure you've got many questions about how far can Abstract Layer go.
Error Handling Handle all kind of errors when loading your table view |
Passing Data You have FULL access to parsed data, so passing it is very simple |
Parameters Be it Header or Body, Static or Dynamic, it's all in Interface Builder |
Pagination Enabling pagination takes less than a minute |
Complex JSON Any form of JSON is supported, no matter how complex it is |
Loader Enable loaders and pull-to-refresh with 2 clicks |
The example below shows how you can fully customize the table view example by modifying the parsed data before displaying it.
Normally, you would create both a Message and a Contact class to pass data between different view controllers.
Create a class called Message:
import Foundation
@objcMembers class Message: NSObject { var id: String? var name: String? var lastMessage: String? var timestamp: NSNumber? }
#import
@interface Message : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *lastMessage; @property (nonatomic, copy) NSString *imageUrl; @property (nonatomic, strong) NSDate *date; @end
Now you only have to type in the name of your class in your attributes inspetor.
That's all. Abstract Layer will now auto-parse the JSON document into your own custom classes. Check the section below to see a use case.
Remember: ALTableView is a subclass of UITableView
Remember: You have FULL access to request and response. Check Custom Request, Pasing Data & Auto Parsing Models
- Create a new class, call it
CustomTableViewCell
- Set the Table view cell class to
CustomTableViewCell
-
Control-drag your date label to the class as a new outlet and call it
dateLabel
-
Don't forget to import
AbstractLayer
to the class's header
- Create a new class, call it
TableViewController
and subclass it formUITableViewController
- Set your table view class in storyboard to
TableViewController
- Download the TableViewController class OR replace the content of the class with the following:
import UIKit import AbstractLayer
class TableViewController: UITableViewController {
// // MARK: Lazy load // private lazy var today:Double = { var calendar = NSCalendar.current calendar.timeZone = NSTimeZone(abbreviation: "UTC")! as TimeZone return calendar.startOfDay(for: Date()).timeIntervalSince1970 }()
private lazy var yesterday:Double = { var calendar = NSCalendar.current calendar.timeZone = NSTimeZone(abbreviation: "UTC")! as TimeZone return calendar.startOfDay(for: Date().addingTimeInterval(-86400)).timeIntervalSince1970 }()
// // MARK: Table view datasource // override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let table = tableView as! ALTableView
if (indexPath.section == 0) { return table.cellForRow(at: indexPath)! } let cell = table.cellForRow(at: indexPath) as! CustomTableViewCell // Get item info let array = table.array[1] as! [Message] let item = array[indexPath.row] // Get item dictionary let timestamp = item.timestamp as! Double // Date calculations if timestamp > today { cell.dateLabel?.text = "Today" } else if timestamp > yesterday { cell.dateLabel?.text = "Yesterday" } return cell
}
override func numberOfSections(in tableView: UITableView) -> Int { return tableView.numberOfSections }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableView.numberOfRows(inSection: section) }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let table = tableView as! ALTableView return table.heightForRow(at: indexPath) }
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let table = tableView as! ALTableView return table.viewForHeader(inSection: section) }
}
#import "TableViewController.h" #import <AbstractLayer/AbstractLayer.h> #import "CustomTableViewCell.h"
@interface TableViewController () @property (nonatomic, assign) NSTimeInterval today,yesterday; @end
@implementation TableViewController
// // UITableView Datasource //
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [tableView numberOfRowsInSection:section]; }
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// Get item info NSArray *array = [(ALTableView *)tableView array].firstObject; NSDictionary *item = array[indexPath.row]; CGFloat timestamp = [item[@"timestamp"] doubleValue];
// Check dates if (timestamp > self.today) { cell.dateLabel.text = @"Today"; } else if (timestamp > self.yesterday) { cell.dateLabel.text = @"Yesterday"; } return cell; }
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [(ALTableView *)tableView heightForRowAtIndexPath:indexPath]; }
// // Lazy Instantiation //
(NSTimeInterval)today { if (!_today) { NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; [calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; _today = [calendar startOfDayForDate:[NSDate date]].timeIntervalSince1970; } return _today; }
(NSTimeInterval)yesterday { if (!_yesterday) { NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; [calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; _yesterday = [[calendar startOfDayForDate:[NSDate date]] dateByAddingTimeInterval:-86400].timeIntervalSince1970; } return _yesterday; }
@end
- Run the app
Abstract Layer is as customizable as anything built from scratch.
As you've seen in the example above, the framework is fully customizable since it's built on top of native Apple UIKit
components like UITableView
& UICollectionView
.
To customize any aspect of Abstract Layer, simply:
- Subclass any of
Abstract Layer
classes to do your customizations - Conform to the
delegate
anddatasource
protocols just as you would do with a regularUITableView
&UICollectionView
Abstract Layer is not a prototyping tool, it's strictly a production-level framework. All of our customers rely on Abstract Layer in their live apps.
Abstract Layer supports lots of features on ALTableView
, so make sure to check them all out!
<tr>
<td class="row-text"><b><a href="/#/menu/table-view/xib" target="_blank">XIB</a></b><p>Reuse cells by designing them in their own XIB</p><br/></td>
<td class="row-text"><b><a href="/#/menu/table-view/authentication" target="_blank">Authentication</a></b><p>JWT is handled automatically once you provide your keys</p><br/></td>
<td class="row-text"><b><a href="/#/menu/table-view/custom-cases" target="_blank">And More...</a></b><p>Check out the dedicated section for custom cases</p><br/></td>
Error Handling Handle all kind of errors when loading your table view |
Passing Data You have FULL access to parsed data, so passing it is very simple |
Parameters Be it Header or Body, Static or Dynamic, it's all in Interface Builder |
Pagination Enabling pagination takes less than a minute |
Complex JSON Any form of JSON is supported, no matter how complex it is |
Loader Enable loaders and pull-to-refresh with 2 clicks |
Download the final project and try it out