From: Alexis Laferrière Date: Tue, 9 Feb 2016 04:51:47 +0000 (-0500) Subject: lib/ios: complete implementation of app::ui with support for ListLayout X-Git-Url: http://nitlanguage.org lib/ios: complete implementation of app::ui with support for ListLayout Signed-off-by: Alexis Laferrière --- diff --git a/lib/ios/ui/ui.nit b/lib/ios/ui/ui.nit index 0b7cc97..5c3aafa 100644 --- a/lib/ios/ui/ui.nit +++ b/lib/ios/ui/ui.nit @@ -37,6 +37,42 @@ in "ObjC" `{ Button_on_click(self.nit_button); } @end + +// Proxy for both delegates of UITableView relaying all callbacks to `nit_list_layout` +@interface UITableViewAndDataSource: NSObject + + // Nit object receiving the callbacks + @property ListLayout nit_list_layout; + + // List of native views added to this list view from the Nit side + @property NSMutableArray *views; +@end + +@implementation UITableViewAndDataSource + + - (id)init + { + self = [super init]; + self.views = [[NSMutableArray alloc] initWithCapacity:8]; + return self; + } + + - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return ListLayout_number_of_sections_in_table_view(self.nit_list_layout, tableView); + } + + - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return ListLayout_number_of_rows_in_section(self.nit_list_layout, tableView, section); + } + + - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return ListLayout_title_for_header_in_section(self.nit_list_layout, tableView, section); + } + + - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + return ListLayout_cell_for_row_at_index_path(self.nit_list_layout, tableView, indexPath); + } +@end `} redef class App @@ -208,3 +244,122 @@ redef class UIButton forControlEvents:UIControlEventTouchUpInside]; `} end + +redef class ListLayout + + redef type NATIVE: UITableView + redef var native = new UITableView(new UITableViewStyle.plain) + + init + do + native.autoresizing_mask + native.assign_delegate_and_data_source self + end + + redef fun add(item) + do + # Adding a view to a UITableView is a bit tricky. + # + # Items are added to the Objective-C view only by callbacks. + # We must store the sub views in local lists while waiting + # for the callbacks. + # + # As usual, we keep the Nity object in `items`. + # But we also keep their native counterparts in a list of + # the `UITableViewAndDataSource` set as `native.delegate`. + # Otherwise the native views could be freed by the Objective-C GC. + + # TODO use an adapter for the app.nit ListLayout closer to what exists + # on both iOS and Android, to support large data sets. + + if item isa View then + add_view_to_native_list(native, item.native) + end + + super + + # Force redraw and trigger callbacks + native.reload_data + end + + private fun add_view_to_native_list(native: UITableView, item: UIView) in "ObjC" `{ + [((UITableViewAndDataSource*)native.delegate).views addObject:item]; + `} + + private fun get_view_from_native_list(native: UITableView, index: Int): UIView in "ObjC" `{ + return [((UITableViewAndDataSource*)native.delegate).views objectAtIndex:index]; + `} + + # Number of sections in this view + # + # By default, we assume that all `items` are in a single section, + # so there is only one section. + # + # iOS callback: `numberOfSectionsInTableView` + protected fun number_of_sections_in_table_view(view: UITableView): Int + do return 1 + + # Number of entries in `section` + # + # By default, we assume that all `items` are in a single section, + # so no matter the section, this returns `items.length`. + # + # iOS callback: `numberOfRowsInSection` + protected fun number_of_rows_in_section(view: UITableView, section: Int): Int + do return items.length + + # Title for `section`, return `new NSString.nil` for no title + # + # By default, this returns no title. + # + # iOS callback: `titleForHeaderInSection` + protected fun title_for_header_in_section(view: UITableView, section: Int): NSString + do return new NSString.nil + + # Return a `UITableViewCell` for the item at `index_path` + # + # By default, we assume that all `items` are in a single section. + # So no matter the depth of the `index_path`, this returns a cell with + # the view at index part of `index_path`. + # + # iOS callback: `cellForRowAtIndexPath` + protected fun cell_for_row_at_index_path(table_view: UITableView, index_path: NSIndexPath): UITableViewCell + do + var reuse_id = "NitCell".to_nsstring + var cell = new UITableViewCell(reuse_id) + + # TODO if there is performance issues, reuse cells with + # the following code, but clear the cell before use. + + #var cell = table_view.dequeue_reusable_cell_with_identifier(reuse_id) + #if cell.address_is_null then cell = new UITableViewCell(reuse_id) + + var index = index_path.index_at_position(1) + var view_native = get_view_from_native_list(table_view, index) + var cv = cell.content_view + cv.add_subview view_native + + return cell + end +end + +redef class UITableView + + # Assign `list_view` as `delegate` and `dataSource`, and pin all references in both GCs + private fun assign_delegate_and_data_source(list_view: ListLayout) + import ListLayout.number_of_sections_in_table_view, + ListLayout.number_of_rows_in_section, + ListLayout.title_for_header_in_section, + ListLayout.cell_for_row_at_index_path in "ObjC" `{ + + UITableViewAndDataSource *objc_delegate = [[UITableViewAndDataSource alloc] init]; + objc_delegate = (__bridge UITableViewAndDataSource*)CFBridgingRetain(objc_delegate); + + objc_delegate.nit_list_layout = list_view; + ListLayout_incr_ref(list_view); + + // Set our + self.delegate = objc_delegate; + self.dataSource = objc_delegate; + `} +end diff --git a/lib/ios/ui/uikit.nit b/lib/ios/ui/uikit.nit index 4e91e21..aa072b8 100644 --- a/lib/ios/ui/uikit.nit +++ b/lib/ios/ui/uikit.nit @@ -527,3 +527,67 @@ extern class UIStackViewAlignment in "ObjC" `{ NSInteger `} new bottom in "ObjC" `{ return UIStackViewAlignmentBottom; `} new last_baseline in "ObjC" `{ return UIStackViewAlignmentLastBaseline; `} end + +# View to display and edit hierarchical lists of information +extern class UITableView in "ObjC" `{ UITableView * `} + super UIView + + # Wraps: `[self initWithFrame:(CGRect)frame style:(UITableViewStyle)style]` + new (style: UITableViewStyle) in "ObjC" `{ + return [[UITableView alloc] initWithFrame: [[UIScreen mainScreen] bounds] style:style]; + `} + + # Wraps: `[self reloadData]` + fun reload_data in "ObjC" `{ [self reloadData]; `} + + # Wraps: `self.autoresizingMask =` + fun autoresizing_mask in "ObjC" `{ + self.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; + `} + + # Wraps: `self.delegate =` + fun delegate=(delegate: UITableViewDelegate) in "ObjC" `{ self.delegate = delegate; `} + + # Wraps: `self.dataSource =` + fun data_source=(source: UITableViewDataSource) in "ObjC" `{ self.dataSource = source; `} + + # Wraps: `[self dequeueReusableCellWithIdentifier]` + fun dequeue_reusable_cell_with_identifier(identifier: NSString): UITableViewCell in "ObjC" `{ + return [self dequeueReusableCellWithIdentifier:identifier]; + `} +end + +# Delegate for a `UITableView` to configure selection, sections, cells and more +extern class UITableViewDelegate in "ObjC" `{ id `} + super NSObject +end + +# Mediator the data model for a `UITableView` +extern class UITableViewDataSource in "ObjC" `{ id `} + super NSObject +end + +# Cell of a `UITableViewCell` +extern class UITableViewCell in "ObjC" `{ UITableViewCell * `} + super NSObject + + new (identifier: NSString) in "ObjC" `{ + return [[UITableViewCell alloc] + initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:identifier]; + `} + + # Wraps: `[self textLabel]` + fun text_label: UILabel in "ObjC" `{ return [self textLabel]; `} + + # Wraps: `[self contentView]` + fun content_view: UIView in "ObjC" `{ return [self contentView]; `} +end + +# Style of a `UITableView` +extern class UITableViewStyle in "ObjC" `{ UITableViewStyle* `} + super NSObject + + new plain in "ObjC" `{ return UITableViewStylePlain; `} + new grouped in "ObjC" `{ return UITableViewStyleGrouped; `} +end