@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
@interface UITableViewAndDataSource: NSObject <UITableViewDelegate, UITableViewDataSource>
// 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;
}
- (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
+
+// View controller associated to an app.nit Window
+@interface NitViewController: UIViewController
+@end
+
+@implementation NitViewController
+ - (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+
+ if (self.isMovingFromParentViewController || self.isBeingDismissed) {
+ extern App app_nit_ios_app;
+ App_after_window_pop(app_nit_ios_app);
+ }
}
@end
`}
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
- redef fun window=(window)
+ 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 push_window(window)
+ do
+ set_view_controller(app_delegate.window, window.native)
+ super
+ end
+
+ # Use iOS ` popViewControllerAnimated`
+ redef fun pop_window
do
- app_delegate.window = window.native
+ manual_pop = true
+ pop_view_controller app_delegate.window
super
end
+
+ private fun pop_view_controller(window: UIWindow) in "ObjC" `{
+ UINavigationController *navController = (UINavigationController*)window.rootViewController;
+ [navController popViewControllerAnimated: YES];
+ `}
+
+ # Is the next `after_window_pop` triggered by a call to `pop_window`?
+ #
+ # Otherwise, it's by the user via the navigation bar "Back" button.
+ private var manual_pop = false
+
+ # Callback when `window` is displayed again
+ private fun after_window_pop
+ do
+ if not manual_pop then window_stack.pop
+ manual_pop = false
+ window.on_resume
+ end
end
redef class AppDelegate
redef type NATIVE: UIView
redef var enabled = null is lazy
+
+ private fun on_ios_event do end
end
redef class CompositeControl
do
super
- var native_view = view.native
- assert native_view isa UIView
- native_view.remove_from_superview
+ if view isa View then
+ view.native.remove_from_superview
+ end
end
end
+# View controller associated to an app.nit `Window`
+extern class NitViewController
+ super UIViewController
+
+ new import App.after_window_pop in "ObjC" `{
+ return [[NitViewController alloc] init];
+ `}
+end
+
redef class Window
- redef type NATIVE: UIWindow
- redef var native = new UIWindow
+ redef type NATIVE: NitViewController
+ redef var native = new NitViewController
- 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
var native_view = view.native
assert native_view isa UIView
- native.add_subview native_view
- fill_whole_window_with(native_view, native)
+ if view isa ListLayout then
+ native.view.add_subview native_view
+ else 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
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
end
redef fun add(view)
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 TextView
+ # Convert `size` from app.nit relative size to iOS font points
+ private fun ios_points(size: nullable Float): Float
+ do
+ size = size or else 1.0
+ return 8.0 + size * 5.0
+ end
end
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 native.size = ios_points(size)
+
+ redef fun align=(align) do native.align = align or else 0.0
+end
+
+redef class UILabel
+
+ private fun size=(points: Float)
+ in "ObjC" `{
+ self.font = [UIFont systemFontOfSize: points];
+ `}
+
+ private fun align=(align: Float)
+ in "ObjC" `{
+ if (align == 0.5)
+ self.textAlignment = NSTextAlignmentCenter;
+ else if (align < 0.5)
+ self.textAlignment = NSTextAlignmentLeft;
+ else//if (align > 0.5)
+ self.textAlignment = NSTextAlignmentRight;
+ `}
+end
+
+# On iOS, check boxes are a layout composed of a label and an `UISwitch`
+redef class CheckBox
+
+ redef type NATIVE: UIStackView
+ redef fun native do return layout.native
+
+ # Root layout implementing this check box
+ var layout = new HorizontalLayout(parent=self.parent)
+
+ # Label with the text
+ var lbl = new Label(parent=layout)
+
+ # `UISwitch` acting as the real check box
+ var ui_switch: UISwitch is noautoinit
+
+ 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.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
+ redef fun text do return lbl.text
+
+ redef fun is_checked do return ui_switch.on
+ 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 fun text=(text) do native.text = (text or else "").to_nsstring
redef fun text do return native.text.to_s
+
+ redef fun is_password=(value)
+ do
+ native.secure_text_entry = value or else false
+ super
+ end
+
+ redef fun size=(size) do native.size = ios_points(size)
+
+ redef fun align=(align) do native.align = align or else 0.0
+
+ # Set the placeholder text, shown in light gray when the field is empty
+ fun placeholder=(text: Text) do native.placeholder = text.to_nsstring
+end
+
+redef class UITextField
+
+ private fun size=(points: Float)
+ in "ObjC" `{
+ self.font = [UIFont systemFontOfSize: points];
+ `}
+
+ private fun align=(align: Float)
+ in "ObjC" `{
+ if (align == 0.5)
+ self.textAlignment = NSTextAlignmentCenter;
+ else if (align < 0.5)
+ self.textAlignment = NSTextAlignmentLeft;
+ else//if (align > 0.5)
+ self.textAlignment = NSTextAlignmentRight;
+ `}
end
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
+
+ redef fun size=(size) do native.title_label.size = ios_points(size)
+
+ redef fun align=(align) do native.title_label.align = align or else 0.0
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:)
`}
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
+
+ redef fun parent=(parent)
+ do
+ super
+
+ var root_view
+ if parent isa Window then
+ root_view = parent.native.view
+ else if parent isa View then
+ root_view = parent.native
+ else return
+
+ # Setup scroll view
+ var native_scroll_view = native
+ native_scroll_view.translates_autoresizing_mask_into_constraits = false
+ native_add_constraints(root_view, native_scroll_view)
+
+ # Setup stack_view
+ native_stack_view.translates_autoresizing_mask_into_constraits = false
+ native_stack_view.axis = new UILayoutConstraintAxis.vertical
+ native_scroll_view.add_subview native_stack_view
+ native_add_constraints(native_scroll_view, native_stack_view)
+ native_lock_vertical_scroll(native_scroll_view, native_stack_view)
+ end
+
+ # Add constraints to lock the vertical and horizontal dimensions
+ private fun native_add_constraints(root_view: UIView, nested_view: UIView)
+ in "ObjC" `{
+ [root_view addConstraints:[NSLayoutConstraint
+ constraintsWithVisualFormat: @"V:|-0-[nested_view]-0-|"
+ options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"nested_view": nested_view}]];
+ [root_view addConstraints:[NSLayoutConstraint
+ constraintsWithVisualFormat: @"H:|-0-[nested_view]-0-|"
+ options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"nested_view": nested_view}]];
+ `}
+
+ # Add a constraint to lock to the scroll vertically
+ private fun native_lock_vertical_scroll(scroll_view: UIScrollView, stack_view: UIStackView)
+ in "ObjC" `{
+ [scroll_view addConstraint: [scroll_view.widthAnchor constraintEqualToAnchor:stack_view.widthAnchor]];
+ `}
+
+ 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)
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;
self.dataSource = objc_delegate;
`}
end
+
+redef class Text
+ redef fun open_in_browser do to_nsstring.native_open_in_browser
+end
+
+redef class NSString
+ private fun native_open_in_browser
+ in "ObjC" `{
+ NSURL *nsurl = [NSURL URLWithString: self];
+ [[UIApplication sharedApplication] openURL: nsurl];
+ `}
+end