X-Git-Url: http://nitlanguage.org diff --git a/lib/ios/ui/ui.nit b/lib/ios/ui/ui.nit index ad6df0d..48c7e12 100644 --- a/lib/ios/ui/ui.nit +++ b/lib/ios/ui/ui.nit @@ -25,16 +25,16 @@ in "ObjC" `{ @interface NitCallbackReference: NSObject // Nit object target of the callbacks from UI events - @property (nonatomic) Button nit_button; + @property (nonatomic) View nit_view; // Actual callback method - -(void) nitOnEvent: (UIButton*) sender; + -(void) nitOnEvent: (UIView*) sender; @end @implementation NitCallbackReference - -(void) nitOnEvent: (UIButton*) sender { - Button_on_click(self.nit_button); + -(void) nitOnEvent: (UIView*) sender { + View_on_ios_event(self.nit_view); } @end @@ -42,7 +42,7 @@ in "ObjC" `{ @interface UITableViewAndDataSource: NSObject // Nit object receiving the callbacks - @property ListLayout nit_list_layout; + @property TableView nit_list_layout; // List of native views added to this list view from the Nit side @property NSMutableArray *views; @@ -58,19 +58,19 @@ in "ObjC" `{ } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return ListLayout_number_of_sections_in_table_view(self.nit_list_layout, tableView); + return TableView_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); + return TableView_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); + return TableView_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); + return TableView_cell_for_row_at_index_path(self.nit_list_layout, tableView, indexPath); } @end `} @@ -78,16 +78,53 @@ in "ObjC" `{ redef class App redef fun did_finish_launching_with_options do + app_delegate.window = new UIWindow + app_delegate.window.background_color = new UIColor.white_color super - window.native.make_key_and_visible + app_delegate.window.make_key_and_visible return true end + private fun set_view_controller(window: UIWindow, native: UIViewController) + in "ObjC" `{ + // Set the required root view controller + UINavigationController *navController = (UINavigationController*)window.rootViewController; + + if (navController == NULL) { + navController = [[UINavigationController alloc]initWithRootViewController:native]; + navController.edgesForExtendedLayout = UIRectEdgeNone; + + // Must be non-translucent for the controls to be placed under + // (as in Y axis) of the navigation bar. + navController.navigationBar.translucent = NO; + + window.rootViewController = navController; + } + else { + [navController pushViewController:native animated:YES]; + } + + native.edgesForExtendedLayout = UIRectEdgeNone; + `} + redef fun window=(window) do - app_delegate.window = window.native + set_view_controller(app_delegate.window, window.native) super end + + # Use iOS ` popViewControllerAnimated` + redef fun pop_window + do + window_stack.pop + pop_view_controller app_delegate.window + window.on_resume + end + + private fun pop_view_controller(window: UIWindow) in "ObjC" `{ + UINavigationController *navController = (UINavigationController*)window.rootViewController; + [navController popViewControllerAnimated: YES]; + `} end redef class AppDelegate @@ -112,6 +149,8 @@ redef class View redef type NATIVE: UIView redef var enabled = null is lazy + + private fun on_ios_event do end end redef class CompositeControl @@ -128,10 +167,14 @@ end redef class Window - redef type NATIVE: UIWindow - redef var native = new UIWindow + redef type NATIVE: UIViewController + redef var native = new UIViewController - init do native.background_color = new UIColor.white_color + # Title of this window + fun title: String do return native.title.to_s + + # Set the title of this window + fun title=(title: String) do native.title = title.to_nsstring redef fun add(view) do @@ -139,26 +182,9 @@ redef class Window var native_view = view.native assert native_view isa UIView - native.add_subview native_view - fill_whole_window_with(native_view, native) + native.view = native_view end - - private fun fill_whole_window_with(native: UIView, window: UIWindow) - in "ObjC" `{ - // Hard coded borders including the top bar - // FIXME this may cause problems with retina devices - [window addConstraints:[NSLayoutConstraint - constraintsWithVisualFormat: @"V:|-24-[view]-8-|" - options: 0 metrics: nil views: @{@"view": native}]]; - [window addConstraints:[NSLayoutConstraint - constraintsWithVisualFormat: @"H:|-8-[view]-8-|" - options: 0 metrics: nil views: @{@"view": native}]]; - - // Set the required root view controller - window.rootViewController = [[UIViewController alloc]initWithNibName:nil bundle:nil]; - window.rootViewController.view = native; - `} end redef class Layout @@ -169,8 +195,6 @@ redef class Layout init do native.alignment = new UIStackViewAlignment.fill - native.distribution = new UIStackViewDistribution.fill_equally - native.translates_autoresizing_mask_into_constraits = false # TODO make customizable native.spacing = 4.0 @@ -187,11 +211,19 @@ redef class Layout end redef class HorizontalLayout - redef init do native.axis = new UILayoutConstraintAxis.horizontal + redef init + do + native.axis = new UILayoutConstraintAxis.horizontal + native.distribution = new UIStackViewDistribution.fill_equally + end end redef class VerticalLayout - redef init do native.axis = new UILayoutConstraintAxis.vertical + redef init + do + native.axis = new UILayoutConstraintAxis.vertical + native.distribution = new UIStackViewDistribution.equal_spacing + end end redef class Label @@ -201,6 +233,31 @@ redef class Label redef fun text=(text) do native.text = (text or else "").to_nsstring redef fun text do return native.text.to_s + + redef fun size=(size) + do + size = size or else 1.0 + var points = 8.0 + size * 8.0 + set_size_native(native, points) + end + + private fun set_size_native(native: UILabel, points: Float) + in "ObjC" `{ + native.font = [UIFont systemFontOfSize: points]; + `} + + redef fun align=(align) do set_align_native(native, align or else 0.0) + + private fun set_align_native(native: UILabel, align: Float) + in "ObjC" `{ + + if (align == 0.5) + native.textAlignment = NSTextAlignmentCenter; + else if (align < 0.5) + native.textAlignment = NSTextAlignmentLeft; + else//if (align > 0.5) + native.textAlignment = NSTextAlignmentRight; + `} end # On iOS, check boxes are a layout composed of a label and an `UISwitch` @@ -218,15 +275,20 @@ redef class CheckBox # `UISwitch` acting as the real check box var ui_switch: UISwitch is noautoinit - init do + redef fun on_ios_event do notify_observers new ToggleEvent(self) + + init + do # Tweak the layout so it is centered - layout.native.distribution = new UIStackViewDistribution.fill_proportionally - layout.native.alignment = new UIStackViewAlignment.center + layout.native.distribution = new UIStackViewDistribution.equal_spacing + layout.native.alignment = new UIStackViewAlignment.fill layout.native.layout_margins_relative_arrangement = true var s = new UISwitch native.add_arranged_subview s ui_switch = s + + ui_switch.set_callback self end redef fun text=(text) do lbl.text = text @@ -236,6 +298,23 @@ redef class CheckBox redef fun is_checked=(value) do ui_switch.set_on_animated(value, true) end +redef class UISwitch + # Register callbacks on this switch to be relayed to `sender` + private fun set_callback(sender: View) + import View.on_ios_event in "ObjC" `{ + + NitCallbackReference *ncr = [[NitCallbackReference alloc] init]; + ncr.nit_view = sender; + + // Pin the objects in both Objective-C and Nit GC + View_incr_ref(sender); + ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr); + + [self addTarget:ncr action:@selector(nitOnEvent:) + forControlEvents:UIControlEventValueChanged]; + `} +end + redef class TextInput redef type NATIVE: UITextField @@ -258,25 +337,25 @@ redef class Button init do native.set_callback self + redef fun on_ios_event do notify_observers new ButtonPressEvent(self) + redef fun text=(text) do if text != null then native.title = text.to_nsstring redef fun text do return native.current_title.to_s - private fun on_click do notify_observers new ButtonPressEvent(self) - redef fun enabled=(enabled) do native.enabled = enabled or else true redef fun enabled do return native.enabled end redef class UIButton # Register callbacks on this button to be relayed to `sender` - private fun set_callback(sender: Button) - import Button.on_click in "ObjC" `{ + private fun set_callback(sender: View) + import View.on_ios_event in "ObjC" `{ NitCallbackReference *ncr = [[NitCallbackReference alloc] init]; - ncr.nit_button = sender; + ncr.nit_view = sender; // Pin the objects in both Objective-C and Nit GC - Button_incr_ref(sender); + View_incr_ref(sender); ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr); [self addTarget:ncr action:@selector(nitOnEvent:) @@ -284,8 +363,50 @@ redef class UIButton `} end +# On iOS, implemented by a `UIStackView` inside a ` UIScrollView` redef class ListLayout + redef type NATIVE: UIScrollView + redef var native = new UIScrollView + + # Real container of the subviews, contained within `native` + var native_stack_view = new UIStackView + + init + do + native_stack_view.translates_autoresizing_mask_into_constraits = false + native_stack_view.axis = new UILayoutConstraintAxis.vertical + native_stack_view.alignment = new UIStackViewAlignment.fill + native_stack_view.distribution = new UIStackViewDistribution.equal_spacing + native_stack_view.spacing = 4.0 + + native.add_subview native_stack_view + native_add_constraints(native, native_stack_view) + end + + private fun native_add_constraints(scroll_view: UIScrollView, stack_view: UIStackView) in "ObjC" `{ + [scroll_view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat: @"V:|-8-[view]-8-|" + options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"view": stack_view}]]; + [scroll_view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat: @"H:|-8-[view]" + options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"view": stack_view}]]; + `} + + redef fun add(view) + do + super + + if view isa View then + native_stack_view.add_arranged_subview view.native + end + end +end + +# iOS specific layout using a `UITableView`, works only with simple children views +class TableView + super CompositeControl + redef type NATIVE: UITableView redef var native = new UITableView(new UITableViewStyle.plain) @@ -385,17 +506,17 @@ 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" `{ + private fun assign_delegate_and_data_source(list_view: TableView) + import TableView.number_of_sections_in_table_view, + TableView.number_of_rows_in_section, + TableView.title_for_header_in_section, + TableView.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); + TableView_incr_ref(list_view); // Set our self.delegate = objc_delegate;