CM3050 Topic 04: Advanced User Interface Elements
Main Info
Title: Advanced User Interface Elements
Teachers: Joe McAlister
Semester Taken: April 2022
Parent Module: cm3050: Mobile Development
Description
In this topic, we look at using advanced UI elements and evaluate how to make the correct decisions between different types of component.
Key Reading
Supplementary Reading
Lab Summaries
The first lab has you experiment with table views. Code is here: CM3050 Lab 4105: Table Views
The gestures lab has you render a circle based on its position. Code is here: CM3050 Lab 4504: Conditional Boundaries
The alert lab has you write a quiz. Code is here: CM3050 Lab 4604: Alert Quiz
The timers lab has you build a stopwatch. Code is here: CM3050 Lab 4704: Stopwatch
Lecture Summaries
4.1 Table Views
Table views are among the main building blocks of mobile apps, they’re everywhere. They don’t always look like tables…
They have a stigma, rows and columns, boring! But think of Twitter, the tweets are just cells in a table. These views are flexible.
RN doesn’t provide a native table view component. There are several available. We’ll use this one as it is pure CSS.
Table views are comprised of sections and cells.
A section is a grouping of cells. For example you will see ‘General’, ‘Display’, etc.
Within these sections are cells which allow you display more granular info, like “General > About”.
Sections are strongly recommended.
Here’s the basic structure of a table view:
<TableView>
<Section>
<Cell title="Cell 1/" />
<Cell title="Cell 2/" />
</Section>
</TableView>
Check out all the props that can be passed.
Typically we’ll want to iterate through some data structure to create the cells. We’ll do this with map in JSX:
let ourIterable = ["thing one", "thing two"]
return (
{ourIterable.map((el, i) => (<Cell title={el} />))})
Note the braces surrounding the map expression, these are essential.
To create custom cells you use the cellContentView
prop in the Cell
component. Like this:
<Cell
cellContentView={
<View>
<Text>Testing!</Text>
</View>
}
/>
You can just define it as a custom function component like this:
const MyCustomCell = (props) => (
<Cell
{...props}
cellContentView = {
//my custom cell view
}
/>
)
Then you can just use <MyCustomCell>
as a component.
Note that you should always wrap Table views in a scroll view.
4.2 Scroll Views and Performance
Scroll views allow us to display content that wouldn’t otherwise fit on screen.
But scroll views must have a bounded height to work (100% or flex). You can set this directly, but this is strongly discouraged. Instead ensure all parent views have a bounded height.
Performance is a key element of scroll views. Drops in frame rate are really notable when scrolling.
They render all child elements even if they’re not currently displayed. This can eat memory fast.
The answer to this problem is FlatList
, it loads items lazily, and offloads them after it’s been offscreen for a while. See the docs
To use them just wrap the items you want to scroll in the component.
Sometimes you’ll see issues in performance, janky scrolling, or ‘jumpy’ animation. How do we debug this?
It’s all about frames. iOS displays at 60fps. This gives iOS roughly 16ms to generate each frame and display it. If it can’t do that, it will drop the frame. This makes the performance appear jumpy or unresponsive.
If you’re building in debug mode, you can shake your device to see a react native debug menu. Here you can display the performance monitor. You can see the memory usage and frame rates.
One frame rate shows the JavaScript thread, the other the main UI thread.
The JS thread is where the majority of RN code is executed. If a piece of JS took longer than the frame rate to execute, we would consider it a dropped frame. If it controlled an animation it would appear glitchy, a press event may be unresponsive.
The UI mean thread executes code natively outside RN. It’s unlikely you use it often, but some libraries like NavigatorIOS will use these native hooks rather than the JS thread.
4.4 Animations
Animations are a key part of mobile design, changing look and feel and providing user feedback on actions.
There are a lot of animations built into the mobile OS, app launchers, elastic banding during scrolling, sleep/wake. But we still need to make our own.
The Animated
API allows for simple time-based animations, you can use it with View, Text, Image, ScrollView, FlatList and SectionList components.
You can create more complex animations with Animated.createAnimatedComponent
To create some animated text for example you would create a custom component that included an <Animated.Text>
component. The regular Text component is read only, so you can’t updated its props without re-making it. We use a ref for the value of eg the opacity that we want to change, and the useEffect
hook to create the timing. Default timing is ease-in-out.
So the process is:
Create a new
Animated.Text
element that has its opacity equal to a new useRef textOpacity which is initialised to 0.React.useEffect
andAnimated.timing
increase the value of textOpacity to the target using an easeInOut curve (or other specified) over the set duration.
Keep animations short, long animations annoy users.
For more detail read the docs
4.5 Gestures
Gestures are an integral part of the app experience, determining when the app feels smooth, or slow and glitchy.
At the heart of RN is the gesture responder system, which manages the lifecycle of gestures in your app.
Gestures are complex, so this provides a simplification, abstracting scrolls, slides, and taps. It also detects multi-touch events.
IOS ignores a lot of touches, for example accidental palm touches. From the RN docs:
To make your app feel great every action should have the following attributes:
- Feedback/highlighting - show the user what is handling their touch, and what will happen when they release the gesture.
- Cancel-ability - when making an action, the user should be able to abort it mid-touch by dragging their finger away.
These features make users more comfortable while using an app, because it allows people to experiment and interact without fear of making mistakes.
PanResponder
is a means of reconciling several touches into a single gesture. docs. It provides a wrapper for the responder handlers provided by the gesture responder system. For each handler it provides a new gestureState object, alongside the native event object.
The gestureState object has an ID, along with properties tracking the number of touches, the last co-ordinates of the touch, the screen coordinates of the responder grant, the accumulated distance, the gesture velocity.
We typically use the PanResponder along with the Animated API to control onscreen animations.
TouchableOpacity can respond to simple gestures, but otherwise we use PanResponder. EG if you want to drag a circle around the screen.
4.6 Alerts
When do we use alerts? When the user needs immediate feedback, to stop and act. EG if they are shopping but an item has now gone out of stock.
Also use an alert when you need feedback and the user chooses between limited options, eg tracking privacy notification.
The simplest alert is Alert.alert("message")
You can also pass the form Alert.alert(title, message, buttons, options)
Title is the dialogue title, set to null if you want to hide it.
Message is an optional message.
Buttons is an optional array with button configs
options is Android only config, ignored if compile target is iOS.
The button array looks like this:
[{
text: "OK",
onPress: () => console.log('ok pressed')
},
{
text: "cancel",
onPress: () => console.log('cancel pressed'),
style: "cancel"
}
]
in iOS there are three style otpions: “default”, “cancel”, “destructive”, ignored for Android.
On Android, you can have a max of 3 buttons, they have a rule of ‘neutral’, ‘negative’, and ‘positive’ buttons.
If you specify one button, it will be ‘positive’. If two it will be ‘negative’ and ‘positive’. If three ‘neutral’, ‘negative’, ‘positive’.
Android alerts can be dismissed by click outside. with the options parameter {cancelable: true}
It’s false by default. If you want to handle the cancel you pass a onDismiss
callback inside the options.
4.7 Timers
Timers let us execute code after a set delay. EG show a prompt then have it disappear after a while.
setTimeout
executes code once after a number of milliseconds. setTimeout( () => alert('hi'), 1000)
.
clearTimeout
stops the timeout after we’ve started it. setTimeout returns an id, then you can pass that id to clearTimeout to clear it.
setInterval
is similar, but it will execute the code more than once, on the elapse of the interval provided. Use case example: updating a counter display in a stopwatch. Polling a webserver every few minutes.
clearInterval
will stop the interval running.
setImmediate
runs code at the end of the current JS execution block. This is as fast as possible async. clearImmediate
will stop it.
requestAnimationFrame
has zero millisecond delay, it will execute after all frames have flushed. This can help with animations, as it will fire at the optimal time, after frames have flushed. Leads to smoother animations and UIs.
cancelAnimationFrame
note that it’s not clear
!