Implementation of app::ui for iOS

Introduced classes

extern class NitViewController

ios :: NitViewController

View controller associated to an app.nit Window
class TableView

ios :: TableView

iOS specific layout using a UITableView, works only with simple children views

Redefined classes

redef class App

ios :: ui $ App

App subclasses are cross-platform applications
redef extern class AppDelegate

ios :: ui $ AppDelegate

Application interface to the iOS system
redef class Button

ios :: ui $ Button

A pressable button, raises ButtonPressEvent
redef class CheckBox

ios :: ui $ CheckBox

On iOS, check boxes are a layout composed of a label and an UISwitch
redef class CompositeControl

ios :: ui $ CompositeControl

A Control grouping other controls
redef class Control

ios :: ui $ Control

A control implementing the UI
redef class HorizontalLayout

ios :: ui $ HorizontalLayout

An horizontal linear organization
redef class Label

ios :: ui $ Label

A simple text label
redef abstract class Layout

ios :: ui $ Layout

A layout to visually organize Controls
redef class ListLayout

ios :: ui $ ListLayout

On iOS, implemented by a UIStackView inside a UIScrollView
redef extern class NSString

ios :: ui $ NSString

String of the Foundation Kit
redef abstract class Text

ios :: ui $ Text

High-level abstraction for all text representations
redef class TextInput

ios :: ui $ TextInput

A control for the user to enter custom text
redef abstract class TextView

ios :: ui $ TextView

A control displaying some text
redef extern class UIButton

ios :: ui $ UIButton

Button on the touch screen
redef extern class UILabel

ios :: ui $ UILabel

Read-only text view
redef extern class UISwitch

ios :: ui $ UISwitch

On/Off button
redef extern class UITableView

ios :: ui $ UITableView

View to display and edit hierarchical lists of information
redef extern class UITextField

ios :: ui $ UITextField

Editable text view
redef class VerticalLayout

ios :: ui $ VerticalLayout

A vertical linear organization
redef abstract class View

ios :: ui $ View

A visible Control
redef class Window

ios :: ui $ Window

A window, root of the Control tree

All class definitions

redef class App

ios :: ui $ App

App subclasses are cross-platform applications
redef extern class AppDelegate

ios :: ui $ AppDelegate

Application interface to the iOS system
redef class Button

ios :: ui $ Button

A pressable button, raises ButtonPressEvent
redef class CheckBox

ios :: ui $ CheckBox

On iOS, check boxes are a layout composed of a label and an UISwitch
redef class CompositeControl

ios :: ui $ CompositeControl

A Control grouping other controls
redef class Control

ios :: ui $ Control

A control implementing the UI
redef class HorizontalLayout

ios :: ui $ HorizontalLayout

An horizontal linear organization
redef class Label

ios :: ui $ Label

A simple text label
redef abstract class Layout

ios :: ui $ Layout

A layout to visually organize Controls
redef class ListLayout

ios :: ui $ ListLayout

On iOS, implemented by a UIStackView inside a UIScrollView
redef extern class NSString

ios :: ui $ NSString

String of the Foundation Kit
extern class NitViewController

ios $ NitViewController

View controller associated to an app.nit Window
class TableView

ios $ TableView

iOS specific layout using a UITableView, works only with simple children views
redef abstract class Text

ios :: ui $ Text

High-level abstraction for all text representations
redef class TextInput

ios :: ui $ TextInput

A control for the user to enter custom text
redef abstract class TextView

ios :: ui $ TextView

A control displaying some text
redef extern class UIButton

ios :: ui $ UIButton

Button on the touch screen
redef extern class UILabel

ios :: ui $ UILabel

Read-only text view
redef extern class UISwitch

ios :: ui $ UISwitch

On/Off button
redef extern class UITableView

ios :: ui $ UITableView

View to display and edit hierarchical lists of information
redef extern class UITextField

ios :: ui $ UITextField

Editable text view
redef class VerticalLayout

ios :: ui $ VerticalLayout

A vertical linear organization
redef abstract class View

ios :: ui $ View

A visible Control
redef class Window

ios :: ui $ Window

A window, root of the Control tree
package_diagram ios::ui ui app::ui ui ios::ui->app::ui ios::uikit uikit ios::ui->ios::uikit app::app_base app_base app::ui->app::app_base ios ios ios::uikit->ios ...app::app_base ... ...app::app_base->app::app_base ...ios ... ...ios->ios a_star-m a_star-m a_star-m->ios::ui

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module app

ios :: app

Basic structure for Nit apps on iOS
module app

app :: app

app.nit is a framework to create cross-platform applications
module app_base

app :: app_base

Base of the app.nit framework, defines App
module array

core :: array

This module introduces the standard array structure.
module assets

app :: assets

Portable services to load resources from the assets folder
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module foundation

cocoa :: foundation

The Foundation Kit provides basic Objective-C classes and structures
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module ios

ios :: ios

iOS platform support
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module platform

ios :: platform

Triggers compilation for the iOS platform
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module ui

app :: ui

Portable UI controls for mobiles apps
module uikit

ios :: uikit

File generated by objcwrapper

Children

module a_star-m

a_star-m

# Implementation of `app::ui` for iOS
module ui

import app::ui

import ios
import uikit

in "ObjC" `{
// Objective-C object receiving callbacks from UI events
@interface NitCallbackReference: NSObject

	// Nit object target of the callbacks from UI events
	@property (nonatomic) View nit_view;

	// Actual callback method
	-(void) nitOnEvent: (UIView*) sender;
@end

@implementation NitCallbackReference

	-(void) nitOnEvent: (UIView*) sender {
		View_on_ios_event(self.nit_view);
	}
@end

// Proxy for both delegates of UITableView relaying all callbacks to `nit_list_layout`
@interface UITableViewAndDataSource: NSObject <UITableViewDelegate, UITableViewDataSource>

	// Nit object receiving the callbacks
	@property TableView 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 TableView_number_of_sections_in_table_view(self.nit_list_layout, tableView);
	}

	- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
		return TableView_number_of_rows_in_section(self.nit_list_layout, tableView, section);
	}

	- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
		return TableView_title_for_header_in_section(self.nit_list_layout, tableView, section);
	}

	- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)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
		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 push_window(window)
	do
		set_view_controller(app_delegate.window, window.native)
		super
	end

	# Use iOS ` popViewControllerAnimated`
	redef fun pop_window
	do
		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

	# The main application window, must be set by `App::on_create`
	fun window: UIWindow in "ObjC" `{ return [self window]; `}

	# The main application window, must be set by `App::on_create`
	fun window=(window: UIWindow) in "ObjC" `{ self.window = window; `}
end

redef class Control

	# Native implementation of this control
	fun native: NATIVE is abstract

	# Type of the `native` implementation of this control
	type NATIVE: NSObject
end

redef class View
	redef type NATIVE: UIView

	redef var enabled = null is lazy

	private fun on_ios_event do end
end

redef class CompositeControl

	redef fun remove(view)
	do
		super

		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: NitViewController
	redef var native = new NitViewController

	# 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
		super

		var native_view = view.native
		assert native_view isa UIView

		if view isa ListLayout then
			native.view.add_subview native_view
		else native.view = native_view
	end
end

redef class Layout

	redef type NATIVE: UIStackView
	redef var native = new UIStackView

	init
	do
		native.alignment = new UIStackViewAlignment.fill
	end

	redef fun add(view)
	do
		super

		var native_view = view.native
		assert native_view isa UIView
		self.native.add_arranged_subview native_view
	end
end

redef class HorizontalLayout
	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
		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 type NATIVE: UILabel
	redef var native = new UILabel

	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 type NATIVE: UITextField
	redef var native = new UITextField

	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

	redef type NATIVE: UIButton
	redef var native = new UIButton(new UIButtonType.system)

	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

	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: 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:UIControlEventTouchUpInside];
	`}
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)

	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: 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;
		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
lib/ios/ui/ui.nit:15,1--629,3