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) Button nit_button;
30 // Actual callback method
31 -(void) nitOnEvent: (UIButton*) sender;
34 @implementation NitCallbackReference
36 -(void) nitOnEvent: (UIButton*) sender {
37 Button_on_click(self.nit_button);
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 ListLayout 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 ListLayout_number_of_sections_in_table_view(self.nit_list_layout, tableView);
64 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
65 return ListLayout_number_of_rows_in_section(self.nit_list_layout, tableView, section);
68 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
69 return ListLayout_title_for_header_in_section(self.nit_list_layout, tableView, section);
72 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
73 return ListLayout_cell_for_row_at_index_path(self.nit_list_layout, tableView, indexPath);
79 redef fun did_finish_launching_with_options
82 window
.native
.make_key_and_visible
86 redef fun window
=(window
)
88 app_delegate
.window
= window
.native
93 redef class AppDelegate
95 # The main application window, must be set by `App::on_create`
96 fun window
: UIWindow in "ObjC" `{ return [self window]; `}
98 # The main application window, must be set by `App::on_create
`
99 fun window=(window: UIWindow) in "ObjC" `{ self.window = window; `}
104 # Native implementation of this control
105 fun native
: NATIVE is abstract
107 # Type of the `native` implementation of this control
108 type NATIVE: NSObject
112 redef type NATIVE: UIView
114 redef var enabled
= null is lazy
117 redef class CompositeControl
119 redef fun remove
(view
)
123 var native_view
= view
.native
124 assert native_view
isa UIView
125 native_view
.remove_from_superview
131 redef type NATIVE: UIWindow
132 redef var native
= new UIWindow
134 init do native
.background_color
= new UIColor.white_color
140 var native_view
= view
.native
141 assert native_view
isa UIView
142 native
.add_subview native_view
144 fill_whole_window_with
(native_view
, native
)
147 private fun fill_whole_window_with
(native
: UIView, window
: UIWindow)
149 // Hard coded borders including the top bar
150 // FIXME this may cause problems with retina devices
151 [window addConstraints:[NSLayoutConstraint
152 constraintsWithVisualFormat: @"V:|-24-[view]-8-|"
153 options: 0 metrics: nil views: @{@"view": native}]];
154 [window addConstraints:[NSLayoutConstraint
155 constraintsWithVisualFormat: @"H:|-8-[view]-8-|"
156 options: 0 metrics: nil views: @{@"view": native}]];
158 // Set the required root view controller
159 window.rootViewController = [[UIViewController alloc]initWithNibName:nil bundle:nil];
160 window.rootViewController.view = native;
166 redef type NATIVE: UIStackView
167 redef var native
= new UIStackView
171 native
.alignment
= new UIStackViewAlignment.fill
172 native
.distribution
= new UIStackViewDistribution.fill_equally
173 native
.translates_autoresizing_mask_into_constraits
= false
175 # TODO make customizable
183 var native_view
= view
.native
184 assert native_view
isa UIView
185 self.native
.add_arranged_subview native_view
189 redef class HorizontalLayout
190 redef init do native
.axis
= new UILayoutConstraintAxis.horizontal
193 redef class VerticalLayout
194 redef init do native
.axis
= new UILayoutConstraintAxis.vertical
199 redef type NATIVE: UILabel
200 redef var native
= new UILabel
202 redef fun text
=(text
) do native
.text
= (text
or else "").to_nsstring
203 redef fun text
do return native
.text
.to_s
206 redef class TextInput
208 redef type NATIVE: UITextField
209 redef var native
= new UITextField
211 redef fun text
=(text
) do native
.text
= (text
or else "").to_nsstring
212 redef fun text
do return native
.text
.to_s
217 redef type NATIVE: UIButton
218 redef var native
= new UIButton(new UIButtonType.system
)
220 init do native
.set_callback
self
222 redef fun text
=(text
) do if text
!= null then native
.title
= text
.to_nsstring
223 redef fun text
do return native
.current_title
.to_s
225 private fun on_click
do notify_observers
new ButtonPressEvent(self)
227 redef fun enabled
=(enabled
) do native
.enabled
= enabled
or else true
228 redef fun enabled
do return native
.enabled
232 # Register callbacks on this button to be relayed to `sender`
233 private fun set_callback
(sender
: Button)
234 import Button.on_click
in "ObjC" `{
236 NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
237 ncr.nit_button = sender;
239 // Pin the objects in both Objective-C and Nit GC
240 Button_incr_ref(sender);
241 ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
243 [self addTarget:ncr action:@selector(nitOnEvent:)
244 forControlEvents:UIControlEventTouchUpInside];
248 redef class ListLayout
250 redef type NATIVE: UITableView
251 redef var native
= new UITableView(new UITableViewStyle.plain
)
255 native
.autoresizing_mask
256 native
.assign_delegate_and_data_source
self
261 # Adding a view to a UITableView is a bit tricky.
263 # Items are added to the Objective-C view only by callbacks.
264 # We must store the sub views in local lists while waiting
267 # As usual, we keep the Nity object in `items`.
268 # But we also keep their native counterparts in a list of
269 # the `UITableViewAndDataSource` set as `native.delegate`.
270 # Otherwise the native views could be freed by the Objective-C GC.
272 # TODO use an adapter for the app.nit ListLayout closer to what exists
273 # on both iOS and Android, to support large data sets.
275 if item
isa View then
276 add_view_to_native_list
(native
, item
.native
)
281 # Force redraw and trigger callbacks
285 private fun add_view_to_native_list
(native
: UITableView, item
: UIView) in "ObjC" `{
286 [((UITableViewAndDataSource*)native.delegate).views addObject:item];
289 private fun get_view_from_native_list
(native
: UITableView, index
: Int): UIView in "ObjC" `{
290 return [((UITableViewAndDataSource*)native.delegate).views objectAtIndex:index];
293 # Number of sections in this view
295 # By default, we assume that all `items` are in a single section,
296 # so there is only one section.
298 # iOS callback: `numberOfSectionsInTableView`
299 protected fun number_of_sections_in_table_view
(view
: UITableView): Int
302 # Number of entries in `section`
304 # By default, we assume that all `items` are in a single section,
305 # so no matter the section, this returns `items.length`.
307 # iOS callback: `numberOfRowsInSection`
308 protected fun number_of_rows_in_section
(view
: UITableView, section
: Int): Int
309 do return items
.length
311 # Title for `section`, return `new NSString.nil` for no title
313 # By default, this returns no title.
315 # iOS callback: `titleForHeaderInSection`
316 protected fun title_for_header_in_section
(view
: UITableView, section
: Int): NSString
317 do return new NSString.nil
319 # Return a `UITableViewCell` for the item at `index_path`
321 # By default, we assume that all `items` are in a single section.
322 # So no matter the depth of the `index_path`, this returns a cell with
323 # the view at index part of `index_path`.
325 # iOS callback: `cellForRowAtIndexPath`
326 protected fun cell_for_row_at_index_path
(table_view
: UITableView, index_path
: NSIndexPath): UITableViewCell
328 var reuse_id
= "NitCell".to_nsstring
329 var cell
= new UITableViewCell(reuse_id
)
331 # TODO if there is performance issues, reuse cells with
332 # the following code, but clear the cell before use.
334 #var cell = table_view.dequeue_reusable_cell_with_identifier(reuse_id)
335 #if cell.address_is_null then cell = new UITableViewCell(reuse_id)
337 var index
= index_path
.index_at_position
(1)
338 var view_native
= get_view_from_native_list
(table_view
, index
)
339 var cv
= cell
.content_view
340 cv
.add_subview view_native
346 redef class UITableView
348 # Assign `list_view` as `delegate` and `dataSource`, and pin all references in both GCs
349 private fun assign_delegate_and_data_source
(list_view
: ListLayout)
350 import ListLayout.number_of_sections_in_table_view
,
351 ListLayout.number_of_rows_in_section
,
352 ListLayout.title_for_header_in_section
,
353 ListLayout.cell_for_row_at_index_path
in "ObjC" `{
355 UITableViewAndDataSource *objc_delegate = [[UITableViewAndDataSource alloc] init];
356 objc_delegate = (__bridge UITableViewAndDataSource*)CFBridgingRetain(objc_delegate);
358 objc_delegate.nit_list_layout = list_view;
359 ListLayout_incr_ref(list_view);
362 self.delegate = objc_delegate;
363 self.dataSource = objc_delegate;