Main Technologies
As shown in our research, we have used Swift
as our main language and Xcode
and its toolchains as our IDE in our project.
As shown in our research, we have used Swift
as our main language and Xcode
and its toolchains as our IDE in our project.
In the meantime, we also imported some dependencies in our project to help us build, test and validate our project.
SwiftSoup is a pure Swift
library, cross-platform (macOS, iOS, tvOS, watchOS and Linux!), for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jQuery-like methods (Chatbi, 2020).
We are embedding SwiftSoup as a core part of the project to parse HTML sent from the X5Learn
backend. It empowers us to understand the API changes and adapt to it quickly.
JGProgressHUD is an elegant and simple progress HUD for iOS and tvOS (Gessner, 2020).
We adopt JGProgressHUD heavily in our user-friendly design and refreshing aspect of our application. We utilise this library in all main refresher()
to clearly show the loading progress of our application and keep user informed of what the application is doing on the backend.
Instead of building documentation ourselves, we deployed jazzy, which hooks into Clang
and SourceKit
to use the AST representation of the code and actual code comments for generating documentation (realm/jazzy, 2020).
Moreover, with the touch of Jazzy templates, the documentation matches the look and feel of Apple’s official reference documentation.
Since Xcode 5
, XCTest
framework was introduced to perform unit test and UI Tests for Xcode
projects (XCTest, 2020).
Our team took advantage of the built-in fast framework and wrote all of our test logic including unit tests, integration tests and performance tests on this framework, and archived 90%
+ coverage. This enables us to deliver our product with confidence to our clients.
SwiftFormat is a code library and command-line tool for reformatting swift code on macOS or Linux (Lockwood, 2020).
Our team was faced with the problem that coding styles are different between the 2 of us. Therefore, we decided to use code linter and formatter to make sure our code compilers and adheres to a good tidy standard in general.
Travis CI is a hosted continuous integration service used to build and test software projects (Travis-CI, 2020).
In this project, we followed better CI/CD
practices and pipelined our application's building and testing without human intervention. Travis CI consistently delivers build results and enables us to merge Pull Requests with reliable backup.
Our project at its core is a mobile content application with relatively heavy network load as fetching videos, pdfs, wikichunk data from numerous APIs and sources is embedded in almost every user interaction.
As demonstrated on the left, the loading process typically involves async-ly conducting network requests, retrieving data and then presenting them on the UI interface with a reloadData()
called for a TableView
or a CollectionView
.
This poses a significant issue when multiple tasks are dispatched to another Thread and not cancellable afterwards. For example, user opens up Video A
, and the application dispatches fetchVideo()
, fetchSuggestedContents()
and fetchWikiChunk()
to another thread for async execution, and of course, a reloadData()
on UI for the main thread.
To gain a general understanding of how this would typically work, see the refresher
below for reference.
Following up on the previous example, a user would possibly quit Video A
before all contents finish loading and quickly open up Video B
, or he might repeat it several times to open up couple of other contents.
The traditional async loading strategy would cause a problem here not only as the fetch...()
function stacking up slows the overall network connection, more so as the many awaiting reloadData()
calls would block the main thread from doing its UI related work and causing significant lags.
Our team then came up with a solution to possibly cancel()
some not so useful tasks, e.g. Video A
's task when user is actually looking at another video.
Our first attempt is to build a custom OperationQueue()
that takes in tasks through the MainController.Queue
and manage them from there. However, this is a right step towards our final solution, but not good enough.
The backend from our client is responding to queries with an average of more than 10 seconds, and during that time you would not have access to the dataTask
as it is on a daemon thread. This would still cause resource leaks and uncontrollable tasks if we only stop here, and experiment does show an alleviation of the problem above but not solving it straight-up.
Therefore, as a follow up, we rewrote part of the dataTask
logic and use a FetchSwitch
and a DispatchSemaphore
to capture the "out-of-control" tasks.
With a lambda or Closure
injection shown above, we are able to regain control of the dataTask
and rank, update or cancel them accordingly. More so, we internally rank tasks with a max-heap to make sure we are always dealing with the most crucial task at hand in the application.
Now, ViewControllers
would be able to notify the MainController
of the changes and make OperationTask
queueing much more efficient. We followed the Open Closed Principle and exposed easy-to-use Queue
APIs (see below for example) across the application.
With some comparison in the profiling tool below, we can easily see the reduction of time hangs caused on the Main Thread
, as well as a reduction of dispatch-tasks conducted. This is only tested with loading 3
videos concurrently, greater performance increase can be seen if more videos are loaded. Click on the images below for a clearer comparison.
With some effort, it finally runs as smooth as silk without worrying anything that could possibly block the main UI Thread.
PlayerView
with flexibilityOne thing we noticed during our HCI design phase was that users are generally prefer the a video application that does not let video occupy the whole screen for the majority of time, especially for a learning application like our project.
Therefore, we attempted to implement a draggable PlayerView
with intuitive gestures to minimise/maximise it.
The main challenge here is that you would not be able to use a standard approach like a UIViewController
to handle this type of view, simply because presenting a UIViewController
inside another one would cause not only significant performance issues, but also not being able to implement dragging UIViewController
around.
We decided to think out of the box and used a floating NavView
based UIView
to archive this, as you can see below in the Storyboard
. It is not a fully functional UIViewController
, but instead a UIView
subsequent in the AppScene
of the RootViewController
.
This brought up the issue that we have to control its floating coordinates by ourselves on the screen, yet to consider so many variations of screen sizes on the iOS
and iPadOS
platforms.
To tackle this difficulty, we started with the origin
(centre point) of the View
. From the image below, we utilised known constants of UIScreen.main.bounds
, i.e the size of the displaying screen to figure out the origin of playerView
in its 3 states.
The animation and gesture recognition of the UIView
is a subsequent problem to solve. Otherwise the UIView
would just jump around and very likely uncontrollable.
Likewise, we would have to figure out the exact coordinates to make it look and feel smooth when dragging around. We put forward a fast CGPoint
function positionDuringSwipe()
to calculate its behaviour live with no pre-made animations. See below for implementation details.
Rather than rendering pre-made animations, or fixing the coordinates to a certain point, we took the most difficult approach which is calculate everything by hand. Yet retrospectively, we think its definitely worth it as it yields the best results as well as the smoothest user experience.
Apart from the two detailed main implementation highlights, we provide several other highlights that are worth noting in our product.
The X5Learn
backend only provides users with video's URL. However, it doesn't provide any thumbnails, preview methods or actual video content. To tackle this issue, we created a separate operation with Grand Central Dispatch(Apple, 2009) that aims to
generate thumbnails by fetching the video image at 1 minute mark.
This process is automatically activated when the ViewController
determines that the content is visible by the user, which is selective yet efficient.
Whilst generating the thumbnail, we figured it would be no difference to also preload the first minute of the video, as we are using that part to generate thumbnail already.
Click on the code below to see the code of this implementation.
Dark-mode, as a newly introduced feature in iOS 13
and iPadOS 13
, is greatly welcomed by many users.
We decided at our polishing stage that we will be providing this feature in our application. It seemed we need to re-work most of our icons and images to support the dark-mode as they might be invisible with the dark background.
However, we used the CIFilter
toolkit to invert UIImages
in-code, while taking precautions to preserve the original scale and size of the image.
Click on the code below to see the implementation in code.
As we were developing our project, our client was developing the backend of the project. Hence, certain API
endpoints like bookmark/
or notes/
are unavailable.
To mitigate their development's effect on our progress, we forked their code and kept on making mocked-up endpoints and APIs such that we can develop, test and deliver our application on time.
At our development stage, we are facing the lack of a recommendation system on the backend to show relevant contents for the content on display.
On that note, to provide some placeholder while looking believable, we used the search function on X5Learn
with the content title as input, and archived valid results. This approach has proven to be a great alternative to an actual recommendation system.
See code below for more details.
Copyright © 2019 - 2020 X5GON, Patrick Wu and Yinrui Hu - All rights reserved.