1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Implementation of `app::ui` for iOS
24 // Objective-C object receiving callbacks from UI events
25 @interface NitCallbackReference: NSObject
27 // Nit object target of the callbacks from UI events
28 @property (nonatomic) View nit_view;
30 // Actual callback method
31 -(void) nitOnEvent: (UIView*) sender;
34 @implementation NitCallbackReference
36 -(void) nitOnEvent: (UIView*) sender {
37 View_on_ios_event(self.nit_view);
41 // Proxy for both delegates of UITableView relaying all callbacks to `nit_list_layout
`
42 @interface UITableViewAndDataSource: NSObject <UITableViewDelegate, UITableViewDataSource>
44 // Nit object receiving the callbacks
45 @property TableView nit_list_layout;
47 // List of native views added to this list view from the Nit side
48 @property NSMutableArray *views;
51 @implementation UITableViewAndDataSource
56 self.views = [[NSMutableArray alloc] initWithCapacity:8];
60 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
61 return TableView_number_of_sections_in_table_view(self.nit_list_layout, tableView);
64 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
65 return TableView_number_of_rows_in_section(self.nit_list_layout, tableView, section);
68 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
69 return TableView_title_for_header_in_section(self.nit_list_layout, tableView, section);
72 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
73 return TableView_cell_for_row_at_index_path(self.nit_list_layout, tableView, indexPath);
79 redef fun did_finish_launching_with_options
81 app_delegate
.window
= new UIWindow
82 app_delegate
.window
.background_color
= new UIColor.white_color
84 app_delegate
.window
.make_key_and_visible
88 private fun set_view_controller
(window
: UIWindow, native
: UIViewController)
90 // Set the required root view controller
91 UINavigationController *navController = (UINavigationController*)window.rootViewController;
93 if (navController == NULL) {
94 navController = [[UINavigationController alloc]initWithRootViewController:native];
95 navController.edgesForExtendedLayout = UIRectEdgeNone;
97 // Must be non-translucent for the controls to be placed under
98 // (as in Y axis) of the navigation bar.
99 navController.navigationBar.translucent = NO;
101 window.rootViewController = navController;
104 [navController pushViewController:native animated:YES];
107 native.edgesForExtendedLayout = UIRectEdgeNone;
110 redef fun window
=(window
)
112 set_view_controller
(app_delegate
.window
, window
.native
)
116 # Use iOS ` popViewControllerAnimated`
120 pop_view_controller app_delegate
.window
124 private fun pop_view_controller
(window
: UIWindow) in "ObjC" `{
125 UINavigationController *navController = (UINavigationController*)window.rootViewController;
126 [navController popViewControllerAnimated: YES];
130 redef class AppDelegate
132 # The main application window, must be set by `App::on_create`
133 fun window
: UIWindow in "ObjC" `{ return [self window]; `}
135 # The main application window, must be set by `App::on_create
`
136 fun window=(window: UIWindow) in "ObjC" `{ self.window = window; `}
141 # Native implementation of this control
142 fun native
: NATIVE is abstract
144 # Type of the `native` implementation of this control
145 type NATIVE: NSObject
149 redef type NATIVE: UIView
151 redef var enabled
= null is lazy
153 private fun on_ios_event
do end
156 redef class CompositeControl
158 redef fun remove
(view
)
162 if view
isa View then
163 view
.native
.remove_from_superview
170 redef type NATIVE: UIViewController
171 redef var native
= new UIViewController
173 # Title of this window
174 fun title
: String do return native
.title
.to_s
176 # Set the title of this window
177 fun title
=(title
: String) do native
.title
= title
.to_nsstring
183 var native_view
= view
.native
184 assert native_view
isa UIView
186 native
.view
= native_view
192 redef type NATIVE: UIStackView
193 redef var native
= new UIStackView
197 native
.alignment
= new UIStackViewAlignment.fill
199 # TODO make customizable
207 var native_view
= view
.native
208 assert native_view
isa UIView
209 self.native
.add_arranged_subview native_view
213 redef class HorizontalLayout
216 native
.axis
= new UILayoutConstraintAxis.horizontal
217 native
.distribution
= new UIStackViewDistribution.fill_equally
221 redef class VerticalLayout
224 native
.axis
= new UILayoutConstraintAxis.vertical
225 native
.distribution
= new UIStackViewDistribution.equal_spacing
230 # Convert `size` from app.nit relative size to iOS font points
231 private fun ios_points
(size
: nullable Float): Float
233 size
= size
or else 1.0
234 return 8.0 + size
* 5.0
240 redef type NATIVE: UILabel
241 redef var native
= new UILabel
243 redef fun text
=(text
) do native
.text
= (text
or else "").to_nsstring
244 redef fun text
do return native
.text
.to_s
246 redef fun size
=(size
) do native
.size
= ios_points
(size
)
248 redef fun align
=(align
) do native
.align
= align
or else 0.0
253 private fun size
=(points
: Float)
255 self.font = [UIFont systemFontOfSize: points];
258 private fun align
=(align
: Float)
261 self.textAlignment = NSTextAlignmentCenter;
262 else if (align < 0.5)
263 self.textAlignment = NSTextAlignmentLeft;
264 else//if (align > 0.5)
265 self.textAlignment = NSTextAlignmentRight;
269 # On iOS, check boxes are a layout composed of a label and an `UISwitch`
272 redef type NATIVE: UIStackView
273 redef fun native
do return layout
.native
275 # Root layout implementing this check box
276 var layout
= new HorizontalLayout(parent
=self.parent
)
278 # Label with the text
279 var lbl
= new Label(parent
=layout
)
281 # `UISwitch` acting as the real check box
282 var ui_switch
: UISwitch is noautoinit
284 redef fun on_ios_event
do notify_observers
new ToggleEvent(self)
288 # Tweak the layout so it is centered
289 layout
.native
.distribution
= new UIStackViewDistribution.equal_spacing
290 layout
.native
.alignment
= new UIStackViewAlignment.fill
291 layout
.native
.layout_margins_relative_arrangement
= true
294 native
.add_arranged_subview s
297 ui_switch
.set_callback
self
300 redef fun text
=(text
) do lbl
.text
= text
301 redef fun text
do return lbl
.text
303 redef fun is_checked
do return ui_switch
.on
304 redef fun is_checked
=(value
) do ui_switch
.set_on_animated
(value
, true)
308 # Register callbacks on this switch to be relayed to `sender`
309 private fun set_callback
(sender
: View)
310 import View.on_ios_event
in "ObjC" `{
312 NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
313 ncr.nit_view = sender;
315 // Pin the objects in both Objective-C and Nit GC
316 View_incr_ref(sender);
317 ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
319 [self addTarget:ncr action:@selector(nitOnEvent:)
320 forControlEvents:UIControlEventValueChanged];
324 redef class TextInput
326 redef type NATIVE: UITextField
327 redef var native
= new UITextField
329 redef fun text
=(text
) do native
.text
= (text
or else "").to_nsstring
330 redef fun text
do return native
.text
.to_s
332 redef fun is_password
=(value
)
334 native
.secure_text_entry
= value
or else false
341 redef type NATIVE: UIButton
342 redef var native
= new UIButton(new UIButtonType.system
)
344 init do native
.set_callback
self
346 redef fun on_ios_event
do notify_observers
new ButtonPressEvent(self)
348 redef fun text
=(text
) do if text
!= null then native
.title
= text
.to_nsstring
349 redef fun text
do return native
.current_title
.to_s
351 redef fun enabled
=(enabled
) do native
.enabled
= enabled
or else true
352 redef fun enabled
do return native
.enabled
354 redef fun size
=(size
) do native
.title_label
.size
= ios_points
(size
)
356 redef fun align
=(align
) do native
.title_label
.align
= align
or else 0.0
360 # Register callbacks on this button to be relayed to `sender`
361 private fun set_callback
(sender
: View)
362 import View.on_ios_event
in "ObjC" `{
364 NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
365 ncr.nit_view = sender;
367 // Pin the objects in both Objective-C and Nit GC
368 View_incr_ref(sender);
369 ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
371 [self addTarget:ncr action:@selector(nitOnEvent:)
372 forControlEvents:UIControlEventTouchUpInside];
376 # On iOS, implemented by a `UIStackView` inside a ` UIScrollView`
377 redef class ListLayout
379 redef type NATIVE: UIScrollView
380 redef var native
= new UIScrollView
382 # Real container of the subviews, contained within `native`
383 var native_stack_view
= new UIStackView
387 native_stack_view
.translates_autoresizing_mask_into_constraits
= false
388 native_stack_view
.axis
= new UILayoutConstraintAxis.vertical
389 native_stack_view
.alignment
= new UIStackViewAlignment.fill
390 native_stack_view
.distribution
= new UIStackViewDistribution.equal_spacing
391 native_stack_view
.spacing
= 4.0
393 native
.add_subview native_stack_view
394 native_add_constraints
(native
, native_stack_view
)
397 private fun native_add_constraints
(scroll_view
: UIScrollView, stack_view
: UIStackView) in "ObjC" `{
398 [scroll_view addConstraints:[NSLayoutConstraint
399 constraintsWithVisualFormat: @"V:|-8-[view]-8-|"
400 options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"view": stack_view}]];
401 [scroll_view addConstraints:[NSLayoutConstraint
402 constraintsWithVisualFormat: @"H:|-8-[view]"
403 options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"view": stack_view}]];
410 if view
isa View then
411 native_stack_view
.add_arranged_subview view
.native
416 # iOS specific layout using a `UITableView`, works only with simple children views
418 super CompositeControl
420 redef type NATIVE: UITableView
421 redef var native
= new UITableView(new UITableViewStyle.plain
)
425 native
.autoresizing_mask
426 native
.assign_delegate_and_data_source
self
431 # Adding a view to a UITableView is a bit tricky.
433 # Items are added to the Objective-C view only by callbacks.
434 # We must store the sub views in local lists while waiting
437 # As usual, we keep the Nity object in `items`.
438 # But we also keep their native counterparts in a list of
439 # the `UITableViewAndDataSource` set as `native.delegate`.
440 # Otherwise the native views could be freed by the Objective-C GC.
442 # TODO use an adapter for the app.nit ListLayout closer to what exists
443 # on both iOS and Android, to support large data sets.
445 if item
isa View then
446 add_view_to_native_list
(native
, item
.native
)
451 # Force redraw and trigger callbacks
455 private fun add_view_to_native_list
(native
: UITableView, item
: UIView) in "ObjC" `{
456 [((UITableViewAndDataSource*)native.delegate).views addObject:item];
459 private fun get_view_from_native_list
(native
: UITableView, index
: Int): UIView in "ObjC" `{
460 return [((UITableViewAndDataSource*)native.delegate).views objectAtIndex:index];
463 # Number of sections in this view
465 # By default, we assume that all `items` are in a single section,
466 # so there is only one section.
468 # iOS callback: `numberOfSectionsInTableView`
469 protected fun number_of_sections_in_table_view
(view
: UITableView): Int
472 # Number of entries in `section`
474 # By default, we assume that all `items` are in a single section,
475 # so no matter the section, this returns `items.length`.
477 # iOS callback: `numberOfRowsInSection`
478 protected fun number_of_rows_in_section
(view
: UITableView, section
: Int): Int
479 do return items
.length
481 # Title for `section`, return `new NSString.nil` for no title
483 # By default, this returns no title.
485 # iOS callback: `titleForHeaderInSection`
486 protected fun title_for_header_in_section
(view
: UITableView, section
: Int): NSString
487 do return new NSString.nil
489 # Return a `UITableViewCell` for the item at `index_path`
491 # By default, we assume that all `items` are in a single section.
492 # So no matter the depth of the `index_path`, this returns a cell with
493 # the view at index part of `index_path`.
495 # iOS callback: `cellForRowAtIndexPath`
496 protected fun cell_for_row_at_index_path
(table_view
: UITableView, index_path
: NSIndexPath): UITableViewCell
498 var reuse_id
= "NitCell".to_nsstring
499 var cell
= new UITableViewCell(reuse_id
)
501 # TODO if there is performance issues, reuse cells with
502 # the following code, but clear the cell before use.
504 #var cell = table_view.dequeue_reusable_cell_with_identifier(reuse_id)
505 #if cell.address_is_null then cell = new UITableViewCell(reuse_id)
507 var index
= index_path
.index_at_position
(1)
508 var view_native
= get_view_from_native_list
(table_view
, index
)
509 var cv
= cell
.content_view
510 cv
.add_subview view_native
516 redef class UITableView
518 # Assign `list_view` as `delegate` and `dataSource`, and pin all references in both GCs
519 private fun assign_delegate_and_data_source
(list_view
: TableView)
520 import TableView.number_of_sections_in_table_view
,
521 TableView.number_of_rows_in_section
,
522 TableView.title_for_header_in_section
,
523 TableView.cell_for_row_at_index_path
in "ObjC" `{
525 UITableViewAndDataSource *objc_delegate = [[UITableViewAndDataSource alloc] init];
526 objc_delegate = (__bridge UITableViewAndDataSource*)CFBridgingRetain(objc_delegate);
528 objc_delegate.nit_list_layout = list_view;
529 TableView_incr_ref(list_view);
532 self.delegate = objc_delegate;
533 self.dataSource = objc_delegate;
538 redef fun open_in_browser
do to_nsstring
.native_open_in_browser
542 private fun native_open_in_browser
544 NSURL *nsurl = [NSURL URLWithString: self];
545 [[UIApplication sharedApplication] openURL: nsurl];