Académique Documents
Professionnel Documents
Culture Documents
Coursework
Documentation
Timetable Application
Contents
Analysis ............................................................................................................................ 5
Overview ................................................................................................................................ 5
Stakeholders ........................................................................................................................... 7
Computation Thinking ............................................................................................................ 8
Thinking Abstractly ............................................................................................................. 8
Thinking Ahead ................................................................................................................... 8
Thinking Procedurally ......................................................................................................... 8
Thinking Logically ................................................................................................................ 8
Thinking Concurrently ........................................................................................................ 9
Application Features....................................................................................................... 10
Research on similar applications .......................................................................................... 10
schedulebuilder.org .......................................................................................................... 10
Timetable (Android application)....................................................................................... 10
Research on Target Demographic ........................................................................................ 11
Questionnaire ................................................................................................................... 11
Justification of Features.................................................................................................... 13
Summary of Features ........................................................................................................... 25
Problems ........................................................................................................................ 27
Requirements ................................................................................................................. 28
Success Criteria .............................................................................................................. 29
Design ............................................................................................................................ 32
Application Structure ........................................................................................................... 32
Structure Chart ................................................................................................................. 32
Wireframe ............................................................................................................................ 33
User Interface ....................................................................................................................... 33
Overview ........................................................................................................................... 33
Splash Screen .................................................................................................................... 33
Icon ................................................................................................................................... 34
Views................................................................................................................................. 34
Database Solution ................................................................................................................ 44
Development Plan ................................................................................................................ 45
Analysis
Overview
I intend to create an application to assist students in timetabling. I plan to do this because
many students find it difficult to keep to a schedule and balance their work and personal
time. Some students may create timetables to manage their time, but this is often a slow
and tedious process. Physical copies are also easily lost.
Schools create timetables for students to follow for lessons, however these timetables are
inflexible and cannot reflect the fact peoples’ lives are in constant flux, nor do they continue
past the school day, so their scope is limited.
Making the timetable digital brings with it many benefits. All students keep a mobile device
on them and therefore can always access a timetable on their devices. The timetable can
also be backed up and therefore is not easily lost. Even in the case the user loses their
phone, it would still be possible to access the timetable on another device. The digital
nature also means that if the user wishes to create physical copies, they can do provided
they have access to a printer. They can then print out multiple copies, which means that
should they wish to use a physical timetable, they can do easily without risking losing their
timetable.
With the correct user interface and user experience, creating the timetable can be made to
be a simple process, which ultimately is the goal of the app – to make creating and following
a timetable easy. The digital format also means that the timetable can be edited with little
difficulty and also quickly. This allows the timetable to be flexible unlike rigid, traditional
timetables. The flexibility means the app will be easy to user in many different
circumstances. It is designed to cater to a variety of different lifestyles.
Many people often set alarms as reminders for events, this can prove tedious for anything
that happens only once, like a one-off event, and can lead to a user’s alarms screen to
become cluttered with a multitude of old alarms that are no longer useful. Some people do
not even think to use the alarms on their phones for reminders and only use it to wake up in
the morning.
Alarms can also be very intrusive and annoying when they suddenly go off.
Notifications are a gentler way of setting reminders, especially for non-urgent events. They
can still serve the same benefit as an alarm – working as a reminder – without the
annoyance of sounds suddenly going off.
Combining these two features is an effective way to help a student stay punctual and stick
to deadlines.
While working, the mobile device can be a large distraction. Notifications for social media or
games will often distract from studying, greatly reducing productivity. A lot of time is lost to
checking social media or playing games, and it is difficult to stop. Prevention from being
Stakeholders
The application is designed around a student’s life. One thing common with all students is
that they attend an educational institution. They also have a certain amount of studying that
they are required to do. However, this varies depending on the level of education that they
are in and the subject or subjects that they are studying.
The application is designed to accommodate for students regardless of their level of
academic progression. This means it will have to be highly flexible and able to fit to any
lifestyle.
The application will also have to be intuitive to use since a range of different ages may find
use in the app. However, in any case, the user will most likely be quite capable with mobile
devices and able to navigate familiar interfaces, so making the application familiar may
make it easier to use.
While the main stakeholders may be students, staff at educational institutions may also
have an interest in this application, as it solves a problem common to many classrooms –
distractedness and disorganisation. The application will serve to prevent mobile devices (at
least) from being a distraction in the classroom by containing the do not disturb mode,
while the schedule will also help ensure that the students remember which lessons they
may have, while also being able to keep notes as a reminder of any work due. As teacher
deal with a large problem of ensuring students consistently remember what they need for
class, this application, with its notification and alarm features, may serve to fix that.
The application also has potential to appeal to the general population due to its flexible
foundation, which means anybody should be able to use the application as a timetable,
despite the fact that the application’s focus is on students.
Although the app targets students, it may very well be open for use by anyone. The typical
user, however, will be aged between 16 and 21, and are currently attending an educational
institution (be it secondary school, college, or university). They will be the kind of person
who always has their phone on them, and may already use their phone frequently to aid
their education, however have often faced difficulty in finding an application that suits their
needs. They also may be seeking how to enhance their learning with the technology they
use on a daily basis. Sometimes, they may find that said technology is distracting, and need
a way to eliminate those distractions, while still gaining the benefits that technology may
provide. Finally, and most of all, they wish to organise their life and studying in a beneficial
way.
I will conduct research about my application using my target demographic. I will also select a
portion of people from my target demographic to aid me in developing this software. They
will work as a focus group, and provide feedback on various features, along with being my
usability testers.
Computation Thinking
In this section, I will discuss other points (that I have not mentioned so far) regarding why
this problem is solvable using computational methods.
Thinking Abstractly
The application abstracts away the specifics of the user’s lifestyle. They may have multiple
tasks to do while attending maths, for example, however the program will only concern
itself with maths occurring at that time and reminding the user that it appears then.
Abstracting the problem helps to simplify it, reducing the complexity and making the
experience easier on the user. The user will not be overwhelmed with reams of unnecessary
information. They only need to focus on the core essentials – being what the event
occurring is, and when it may occur.
Thinking Ahead
To create a schedule for the user, the user will have to enter all the events they wish to
show up. They may choose to only display events during a certain period of time (in which
case it is also ideal to be able to select the range). Furthermore, details about the event are
required – the name, so the user can tell what the event is; the time, so the application
knows when to display it; any alarms, in case the user wishes to be reminded about it.
Note taking, too, requires data input, those being the note title (to differentiate them) and
the body of the note.
Alarms will require the time and date of occurrence, and possibly whether it repeats or has
a different tone.
Printing will require some form of printer set up. The user will have to define which printer
to connect to before any printing can be done.
Thinking Procedurally
The problem can be broken down into various features (schedule, notes etc.). Each of these
can then further be broken down into their components. For example, the schedule may
consist of six parts, where those parts of creating the framework, creating events, editing
events, deleting events, displaying events, and handling conflicts.
Those parts can then be tackled individually, and once each problem is solved, the whole
solution can be put together to from a working product.
Thinking Logically
There are some decisions the software will have to make. When to fire an alarm is a clear
example, as it cannot fire too early or too late. Regarding the alarms, if the alarm is to sound
repeatedly, then the code will fire in loops given a certain condition (usually being that the
user has not dismissed the alarm).
Thinking Concurrently
Concurrent programming in an application of my size does not often occur. It is operating at
a high enough level that concurrency at the machine level is not handled by me, meanwhile
it is not so large that it requires optimisation.
While concurrent programming is unlikely to appear, it is not impossible. Any time delayed
function, for example, may make use of concurrency to carry out other tasks.
Database loads, for example, sometimes take a period of time. In that time, other code may
be executed. Alternatively, the program may be structured so that the database call occurs
early on, with setup code for a view following, allowing the database to load in the
background without a significant wait time added, like if it were called directly at the point it
is needed.
Application Features
Research on similar applications
Here I will discuss applications and service that have set out to solve a similar problem to
the one that I am.
schedulebuilder.org
This web-app shows a single week view, with days along the top and hours on the side.
The number of days and hours shown can be edited in settings.
Adding events consists of writing the name of the activity, the times between which it takes
place, with optional selection of the days it recurs and colours changes. The timetable’s
background can be changed.
It is slightly buggy when editing. For multiply recurring events, using the edit all button
clears any changes and puts everything to the time of the one being edited, if they happen
to be at different times.
It features an undo button which is incredibly useful, however it only undoes one step.
A unique URL can be used to save edits made and access the timetable again at a later date.
The timetable proves tedious to fill out and it only represents one week. Someone on a
biweekly schedule would have to create another timetable from scratch for the second
week.
The application features an intuitive user interface, with a bar that slides across to allow
access to every view quickly.
It also features multiple timetables.
Questionnaire
The following questionnaire will be sent to a sample of my target demographic through
social media like Facebook and Snapchat.
If you had a timetable for school subjects on your phone, how often would you use
it?
o This is the core idea of the app. This question gauges how potentially useful
the application will be at its most basic.
How likely are you to timetable non-school related activities?
o The secondary feature of the application is to work as a timetable outside of
academics. This question is to find whether users have an interest in this
feature.
Would you want to be reminded to do specific activities with an alarm or
notification?
Justification of Features
The Timetable
The timetable is the core feature of the application. The purpose of the timetable is to help
the user create a schedule that is flexible and easy to follow.
One of the most prominent features used is the use of different colours to quickly
distinguish between events.
the whole range of colours available through an RGB or HSV colour pickers.
However, a potential downside is that users may become overwhelmed with the choice
made available to them and end up choosing colours that do not help them distinguish the
events. For example, they may pick varying gradients of blue for all events, making the
distinguishing factor largely useless. In addition, not all user are familiar with the way
RGB/HSV colour pickers work, and they may become confused and unable to use the
feature at all. This may especially be a problem for younger users.
Alternatively, the user may be given a limited selection of colours to choose from, where
each colour is sufficiently different to the others. This ensures each event has a unique
colour, so long as there are enough colours available for every event. This therefore sheds
light on the problem inherent to this approach. If the user opts to create enough events,
there will not be enough colours to accommodate them, which may have a negative impact
on the “viewing at a glance concept” since its job to make viewing the timetable more easily
will have failed.
A final potential issue is that people who suffer from colour-blindness may have difficulty
distinguishing some of the colours. I do not believe this will have a large impact on their
experience of the application since if they have deuteranomaly, as it will make little
difference whether they pick red or green for the event’s colour. This only becomes an issue
if the user suffers from achromatopsia, in which case the colour differentiation serves little
to no use. A colour-blind mode may be added to the application to help mitigate some of
these problems.
An alternative to colour coordination for instant recognisability is using images. This carries
the same benefits and issues to colour-coordination, however I feel that images require
more processing to discern over bold colours (with the exception of colour-blind people,
where the use of images may be of greater benefit). As with colour-coordination, the
question remains as to whether giving users freedom better than not doing so. If given the
freedom, users may be able to upload images unique to the event. If they have a
woodworking class, a picture of the workshop they will be in may be uploaded, which serves
as a better reminder than anything else could. However, there is the potential for users to
upload inappropriate images, which reflects negatively on the user if it is noticed, and also
on the application itself, undermining its point as an application to enhance education. If
Name
Date/Time/Occurrence
Description
Reminders
Colour/Image
The timetable will need some way of resolving scheduling conflicts. It is possible that two
events clash (for example, one ends after another begins, but it is still possible to attend
both).
Before an event is finalised, the application will need to check if its times clash with any
other events. If so, it must warn the user if they would like to continue.
The two clashing events will then be placed side by side if the user wishes to continue.
There will need to be a maximum number of events that can exist in the same timeslot. A
good option is two or three simultaneous events.
Create event
o Button must be easily accessible
o All options needed listed
Name
Datetime/occurrence
Description
Reminders
Colour/image
o Save the event
o A scheduling conflict resolution
Modify
o Tap on event, press edit button
o Edit all features available in create
Delete
o Tap, press delete button
o Deletes event
o Allow for an undo action
The Calendar
The calendar is designed to make setting one-off events more intuitive.
Alarm/Notification
For some events, especially one off events, the user may wish to set reminder so their
phone notifies them before the event so that they can prepare.
Do-Not-Disturb Mode
Phones often make notification sounds, which can be distracting when trying to
concentrate. It is difficult to study with constant interruptions. There is also the chance the
student will completely stop studying and will not go back after being distracted by the
device.
Respondents largely
seemed to agree
that the feature will
be useful to them.
Removing these sorts of distractions may help increase focus and productivity.
iPhones currently come with a do-not-disturb feature, however the point of the feature
within the application is to make it easy to set, and make it last during periods of studying. It
may even be possible to make it set automatically when a person begins a studying session.
Chances are the user will be within the timetabling application while in an educational
environment and therefore being able to quickly access a notepad of some sort without
having to leave the application itself may prove useful.
The note taking will allow the user to create a note, write down text (using a keyboard,
however handwriting and voice recognition may be added), and then save the note.
There is no limit to the size of the notes, or the number of notes created.
The notes can then be deleted when they are no longer useful. A multi-select feature to
copy or delete many notes may also prove useful.
As with the timetable, the delete feature will come with an undo button.
The user may also wish to add a title to their notes, and use formatting options while writing
the notes.
The left shows Evernote’s note
taking interface. Along the
bottom but above the keyboard
is the formatting options. They
allow the user to italicise,
strikethrough, underline text,
along with many more options.
The interface is intuitive to use
and good for quick note taking.
The note-taking feature of my
application will not be as
complex, but may be inspired by
the design choices made here.
Feedback/Insights System
The app will collect data as it is used. From the collection, it will be able to graphically
present the data back to the user so they can observe their usage and habits. The
application itself will also analyse the data and make suggestions to the user.
The application may see when the user is often free, and suggest that period of time be
dedicated to studying, especially if it is near an exam period.
The application may also suggest breaks if the user is attempting to dedicate too much time
to academics.
If the user is spending a lot of time studying one subject whilst neglecting another, they may
not notice themselves. However, the application will be able to point this out, for example
by comparing the time spent studying one subject to the time spent studying the other. Or
compare an individual subject to the average spent, and see whether one or many subjects
do not have the right amount of time spent on them.
This feature relies on the user input into the application, and unless it somehow checks, will
have to assume that the user has strictly followed their timetable.
If the user does not input into the application inputs erroneous data, then the suggestions
made will be unhelpful and incorrect.
Goals
Along with the difficulty rating system, a system of goals may be put into place. The user can
set a goal, or goals, they wish to achieve, and work within the application to complete it.
They may also set milestones within the main goal, so they have a definite sense of
progression while completing the task.
Goals can be long term or short term, depending on how the user sets them.
Other Devices
The timetable need not necessarily be restricted to only one device to be accessed. If the
user does not have their phone nearby then they can access the timetable with another
One respondent made it clear they wished to be able to access the timetable on all devices
they had, however it is unrealistic to develop for such a broad range of different device
types.
I found it surprising that such a small proportion (3.2%) wanted to use the application on
their one mobile device exclusively.
On the other hand, it was not surprising there was demand for a laptop/desktop version of
the application. I had predicted that users might wish to access their timetable on a
computer.
Regarding how the timetable will be displayed on a desktop, it may be simplest to make the
timetable accessible within a web browser.
This circumvents the issue of developing for multiple platforms (namely Windows and
Printing
Responses show that potential users would like the option to print out the timetable.
This may be done in a way where the timetable is exported, as a PDF for example, which can
then be printed by the user.
This method seems simpler than interfacing the application with printers and allowing the
user to print out that way, since there are issues of how the user connects to the printer
(wired or wireless), differences between printer models and manufacturers, and adding in
printing options for the user if they need it.
That feature alone is worth its own entirely separate application (of which there do exist,
although often proprietary and therefore specific to one brand only).
Summary of Features
The application will include the following:
The Calendar
o The user has little need for an additional calendar. Furthermore, the only way
to implement one within the application is using Apple’s API for the default
calendar in iOS. There seems to be little point in creating a redundant
feature.
Cross-device support
o While a feature that many users appeared to want, unfortunately it is not
practical to implement. It would require many aspects to accomplish, among
them being proficiency in development for multiple devices, and an internet
synchronisation solution. This feature would multiply the time to develop a
number of times, while increasing the complexity significantly as the
application is reprogrammed within different environments.
o The application will target only iPhone devices. Although mobile devices
typically have hardware limitations, the application does not carry out any
intensive processes, and is ideal as a mobile application. Apple iPhones share
are owned by 49.37% of smartphone users in the UK (statista.com, May
2018), whilst 90% of 16-24 year olds own a smartphone (ofcom.org, August
2015). This shows that the iPhone is a popular device and the application will
reach a large number of users.
Printing
o While another potentially useful feature, it suffers from similar issues to
cross-device support, where the feature itself may end up larger than the
application itself. Furthermore, the schedule would have to be organised
around the intention of being able to print it, where this may potentially
detract from the user experience by preventing other approaches.
As mentioned in the stakeholders section, I intend to create a focus group to aid with
application development. I was aiming for roughly three to seven people, and received
confirmation of participation from five.
For data protection reasons, they shall not be named. The group as a whole may be
referenced to, or a member within, without any identifying information.
Problems
With all development projects, there is a time cost to be considered. The algorithms for the
project will take time to plan and code, and often they will not work first time, meaning
even more time will be required to fix
Cost –The application will be for iOS devices. This means the user can only use it if they own
an iPhone or iPad.
Availability – Although the iPhone may be a popular device (especially with my target
demographic), it hardly covers all potential users. Although the iPhone is more popular in
the UK and US, worldwide Android enjoys a greater market share by a significant margin. It
is difficult to develop for Android due to software fragmentation and large differences in
hardware capabilities, however it means that a large portion of users have no access to the
application. Furthermore, potential users that prefer desktop/laptop will be completely
alienated, as the application has not been optimised for desktop use.
Although there is the possibility of expanding to provide cross-platform support,
Development for this platform also requires the use of a Mac, which is a somewhat
expensive piece of hardware. Computers capable of running Xcode are also more expensive.
Regarding development, there are potential issues with the timetable itself, for example.
After conducting some brief research, there does not appear to be any framework available
to achieve what I am aiming for, so everything I will likely have to create myself from
scratch.
The Do-Not-Disturb mode may not be viable due to iOS application restrictions and
sandboxing. The OS may prevent the application from enacting system-wide changes like
that. This will require more research.
A similar issue is faced with alarms – they may not be possible due to restrictions in access
to hardware (like the speaker) while the application is not in the foreground.
Goals and insights are together an interesting set of features. It is possible to make an entire
application solely based around those two concepts. For that reason, I believe there is a very
good chance that the features may prove to be too difficult to implement within this
project. Furthermore, there are potential legal issues surrounding this. Since data is
collected about the user, it must be stored in compliance with the Data Protection Act,
meaning adequate security must be provided, for example. The issue becomes even more
complex considering that a large portion of the users may be minors, meaning that the laws
protecting their data are even more stringent. This must be taken into account before
developing these features.
There are some limitations to the application. While it sets out with the aim to provide
structure to the life of a student, there is only so much that can be accomplished by an
application. The application can show the user when events occur, and remind them to
attend those events, however there is no way of enforcing this.
A user may have a period of time dedicated to homework, but there is no way of ensuring
Requirements
Software:
Apple Mac – required to run Xcode. Although Xcode is capable of running on any
device that supports the MacOS requirement, this does not necessarily mean it will
run well. While I am using a MacBook Air, it may struggle to cope with the program
once it begins to become quite large.
A certain amount of storage is also necessary. While I am unsure as to how large the
entire project may reach, a few hundred megabytes may be excessive. Dedicating
200MB to the source files seems reasonable. Of course, this is not a hard rule and
can be expanded if necessary (although this is unlikely).
Apple iPhone – the application targets this device. There may be software version
requirements too, however that is decided when compiling in Xcode. Xcode defaults
to compiling for the latest software version available at the time of its last update,
however compilation for previous version can be done.
Success Criteria
3. Events 1. Display The event must display a title and colour within the table.
The title must be contained within the box, and the box
must have a fixed size (unless it is designed to be variable).
2. Extra The user must be able to add extra information to an
Information event.
3. Reoccurrence An event must be repeatable. The event appear at any slot
within the week. This includes only a single slot, or
multiple.
Conflicts handling must be included to ensure that events
do not overlap.
4. Alarms 1. Function The user must be able to control when an alarm sounds.
and
Notifications
An alarm must sound at the correct time. Sounding
includes any method of attracting the user’s attention. By
default, this is by making a noise.
Alarms that sound must be silenced. The user must have
control over alarm rings.
2. Standalone The user should be able to add alarms that are not
attached to an event or otherwise.
5. Do Not 1. Silent mode A system-wide silent mode must be enacted. This prevents
Disturb any sounds or notification from the device.
2. Control The mode must be enabled and disabled at the user’s
discretion. They could also control the times between
which the silent mode is in place. This could be
implemented to occur automatically with no further input
7. Goals 1. Function The user must be able to create as few as one or as many
goals as they like.
The user must have no limit to the number of notes they
can create.
A goal must take a title, explanation, and time by which it
is to be completed.
A goal must track the amount of progress a user makes
towards completing the goal.
It must then display the amount the goal has been
completed to the user.
2. Milestones A goal could be broken into milestones. This will depend
on whether the user chooses to do so.
Focus group: There was a slight feeling of anticipation within the air as I described my
project in detail (using my success criteria as a reference). They seem to be quite excited
with the prospect of the application beginning development.
Design
Application Structure
Structure Chart
This chart shows the potential structure of the application. The arrows show the direction in
which views are traversed. A route can only be travelled in the reverse direction if the user
has already travelled forwards through it (as if undoing the travel). This represent ‘going
back’ in the application. A route that has not be immediately visited forwards cannot be
travelled down backwards.
It is also worth noting that every view will link back to the Agenda, allowing travel from any
view to those immediately linked by the Agenda. This action works like a reversal of all
routes travelled through, carried out in a single step.
The chart demonstrates how navigation through views will work and the views necessary for
the application. While this may change during development, the underlying structure will
not.
The chart also demonstrates how the application as a whole can be broken into smaller
parts. Development will be focused on each part of the whole, where here, each part
represents a view within the application.
Wireframe
I have used the structure chart to create a wireframe for the application:
User Interface
Overview
The user interface design as a whole is integral to a user’s interaction with the application.
The design affects every aspect of the user’s interaction, and therefore it is important that
the design is and experience provided to the user by the application is intuitive and pleasing
to the eye.
The application is education focused, and therefore I plan to use clear, contrasting colours
and themes to make interaction with the application as simple as possible.
This will enhance the readability of the application. It is less likely the user will have to spend
time searching for a feature if it is obvious and stands out.
The default iOS design consists of white backgrounds with black text and blue items. This
colour scheme is ideal for what I am going for due to the stark contrast of colours. I will be
incorporating this within the majority of my application.
Splash Screen
When the user opens the application they will be greeted with a “splash” screen – a static
image displayed while the application loads.
The splash will set the tone for the application, and give the user a sense of “something”
happening while the application loads.
Icon
The application icon will have to grab the user’s attention so it does not get lost in the
multitude of other applications the user may have. Making it memorable will also help the
user locate the application quickly at a glance. The user can therefore open the application
swiftly.
Views
The application will consist of multiple views the user will interact with (as described in the
structure chart). A mock-up of each view will be created in Xcode.
This serves as:
Timetable
The timetable will be set out such that every event the user may have that week is set out
before them in a schedule. Items can be added or modified, and all are collectively viewed in
the schedule view.
If the user taps on an item, they will be directed to the detail view for the item. There they
can view the item in specific detail. They can also edit any data they wish and delete the
item.
The detail view will consist of all the data the user may need to consider when creating an
event:
Notes
The notes screen is for users to quickly take down notes within the application, since they
will likely be using the application within a school environment.
After navigating to the notes screen, the user will be met
with a list of all their created notes, displaying a title and a
snippet of the note (if it is longer than the length of the
flavour text within the cell).
This allows the user to view their notes quickly and easily.
The title gives them an idea of what the note is about, which
can be ascertained at a glance. Meanwhile the snippet
provides extra information on the note, which the user can
also see quickly, especially compared to the time taken to
open the note and view the full body text themselves.
This feature becomes most useful when the user wishes to
compare the contents of multiple notes quickly, to grasp an
idea of what each note is about, since otherwise they would
have to open the first note, glance at the contents, navigate
out, and begin this process again for each subsequent note.
In addition, if the user were trying to find a specific note but
the title they had set was not reminding them of the
contents, they would have to use the above method to find
the note they were looking for.
Alarm
Alarms can be set alongside events as a reminder. The alarms also work as entities complete
in and of themselves. They can
be used by the user to set
reminders for anything,
regardless of whether the
reminder is attached to an
event or not. The user could
also use the alarms in the
traditional way; however, the
application was not designed for
this use.
The user can navigate to the
alarms screen from the agenda.
Here, they will be presented
with a list of the alarms created.
Each alarm has a time, a note
about the alarm purpose, and a
There is also the button to add a new alarm. This takes the user to the detailed alarm
screen. The user picks the time and date for the alarm to go off in a picker view, and can add
extra information about the alarm. If the user does not fill this in, it will be given a default
text (like “Alarm 1” for example).
After filling in the necessary details, the user can save the alarm or cancel and will be taken
back to the initial alarm screen.
The user may also view the detail alarm screen by tapping “set reminder” in the event
creation screen. When creating an event, the user may wish to set a reminder for the event.
When they do so, the alarm (rather than displaying the current time and date and default
text) will load the date and time of the event, and the event name in the details. This may
save the user some time in setting the alarm (allowing them to just save immediately) in the
case that they want the reminder to go off just as the event begins. There is a potential
issue however. If the events are repeated, then the user will expect that the alarm will go
off before every event. However, since the alarms are individual entities, an alarm will have
to be set for every event. This will cause the view alarms screen to be flooded with alarms
for events that are not happening in the near future.
There are three possible solutions:
1. Separate the reminders for events from the alarm entities. This will allow them to
work by a different system and therefore a method to manage reminders along with
events can be implemented.
This method will require a new system to be implemented to manage these alarms,
and will also make the managing of the alarms by the user more difficult, since they
will not be in the same place.
2. If the user sets the event to be repeated, then the option to set a reminder will be
greyed out and inaccessible. This solution will be very simple to implement, however
it comes at the cost of the user experience, since they are barred from a key function
of the events.
3. The time the alarm plays may dynamically change when the alarm goes off. The
alarm screen displays the alarm for the repeating event as single item, with a note or
symbol indicating it repeats. The alarm stores the interval between the repeated
event (intervals if the repeat is follows and irregular pattern). After the alarm sounds
and the user dismisses the alarm, the alarm will add the interval to the set alarm
time, meaning the alarm goes off for the next event. This method is especially useful
since if the user sets the alarm to go off five minutes prior, this setting of five
minutes prior sounding will be retained. This method may prove complex
programmatically to find the intervals between events, especially if the intervals
follow an irregular sequence before repeating. Intervals may be stored in an array
that can be accessed as a reference for interval points. However, a universal method
of recognising these interval times must be decided. This method will also require
Focus Group:
I demonstrated my wireframe to my focus group. Some concerns were raised at the lack of
detail for the Goals and Feedback views, which I acknowledged. Unfortunately, I believe
these two features may be too ambitious for this project. While I do hope to include them,
as they are two main features, the difficulty surrounding tracking, protecting and then
interpreting the data may prove too large. Therefore, for the time being, they are remaining
low on the priority list.
Database Solution
An important consideration for the application is how data within it will persist.
There are a many solutions available, all often with their own specific way of
implementation. This means that once I make the decision and begin development around
that solution, it will be incredibly difficult to reverse that decision.
This is therefore a decision to be made as well as possible first time round.
First of all, I hope to avoid any SQL database solutions. Although querying has its
advantages, ensuring the application works with SQL correctly and ensuring the safety of
the database will likely prove too large a task to accomplish. For that reason, I am avoiding it
for now.
There are three main options I can consider. I am considering these due to their popularity
(meaning that there is a large support network should there be any issues), their claimed
‘ease of use’ (for a variety of reasons that we shall see), and their cost, or rather, lack
thereof. Database maintenance is often a problem for developers, but for the solutions I am
considering, this is not an issue. The online solution is free to use under a user threshold
(which at that level of usage allows for monetisation to allow the application to support
itself), whilst the offline solutions require no maintenance and handle themselves.
Clean-up of the database internals is not included in ‘maintenance’, and regardless of what I
choose, will have to be handled by me.
The three options are:
CoreData
Firebase
Realm
CoreData – although not traditionally renowned for its ease of use, it is already included
with Xcode and therefore is very easy to implement. The difficulty comes with usage, and
appears to be very specific in the way that it has to be used, making this a potentially
frustrating storage solution to use. Being the default provided within the development
environment, one can assume that many, if not all, Swift-specific features are supported.
Firebase – created by Google, it is an online JSON tree database that is free to use under a
certain user activity threshold. As it is an online service, it comes with the benefits of data
synchronisation. However, since data leaves the device, it must be protected. This includes
authentication by the user before accessing the application. Firebase handles a large portion
of the setup work required, making installation very simple within the application.
An issue lies with the structure however. JSON based databases are not uncommon,
however they may not work with Swift easily. Swift contains no default method of handling
JSON data. To interpret the data sent by the database, a significant amount of work must be
done in translating the received data to a format Swift understands. The difficulty lies in the
repeated translation steps between the database and application. In addition, the database
cannot be queried, meaning that the entire store may have to be downloaded every session
Development Plan
I will work in development cycles, similar to the spiral model.
Each cycle consists of five stages:
1. Planning: the objective of the cycle is decided, which depends on what is required,
and the outcome of previous cycles. The criteria is also outlined, and development
for the cycle ends once the criteria have been met (unless the criteria have been
changed).
2. Design: the algorithms are designed beforehand, in pseudocode or flowcharts where
relevant.
3. Implementation: the designed algorithm is programmed.
Development
Here development of the application will be detailed, following the plan outlined above.
Cycle 0: Setup
This cycle is for initially setting the project up.
Xcode has a significant advantage over other development environments in that it carries
out a large portion of the work for the developer, allowing them to dedicate more time to
programming, and less time considering the specifics of rendering.
Projects in Xcode can be simply created by opening the program, which allows a new project
file to be created. A few basics, like the project name are required.
After this, a Podfile is generated (where the dependencies are placed), along with an
xcworkspace file (which replaces the xcodeproj file created at project start).
Within the Podfile, pod 'RealmSwift' is placed within the application target function. This
allows Realm to be installed within the application.
To finalise the installation, pod install is used in terminal. The manager then handles
pulling all the files required to use Realm into the project.
With all Realm files within the project, the database itself must be instantiated. By doing this
within the app delegate, it can be accessed within every view.
Two lines of code are required: import RealmSwift, followed by let uiRealm = try!
Realm().
Testing
At this point, there is not much that can be tested, besides checking the project compiles
correctly at this stage, which I will carry out immediately.
The compilation was a success, which shows that there are currently no fundamental errors
in the project. It also suggests that the podfile and Realm are working correctly.
Header
In the UI editor, I will create two labels at the top of the view.
I will then make it so these are editable programmatically. In fact, one will be used to
update the user on the current date. This makes use of the Swift date formatter, where the
text displays a converted string of the current date.
The agenda has been created, with two labels connected to the class “Agenda”. The labels
can now be edited programmatically.
Table
Using the UI editor in Xcode, I have created a basic view with two labels and a table.
I have added two labels to the cells within the table view.
The table displays correctly within the view.
Queue
Overview
I will be using a priority queue. A queue works on a first-in-first-out basis, where the first
item added to the queue is the first to exit. The exception to this is where priority queues
are used. In this case, if an item has a greater priority than another does, it will be pushed
ahead in the queue, regardless of when any previous items were added to the queue.
One way to implement a priority queue is to have separate queues for each priority level.
This method is inefficient where there are many priorities. I will likely need a maximum of
three priority levels, so this drawback is not an issue.
My three priority levels will be:
1. The lowest priority. Every item appearing is automatically assigned this priority.
2. The next priority level. A priority of 2 allows an item to appear before any item with
a priority level of 1. Only once all level 2 priority items have left the queue will any
level 1 priority items appear. A priority of 2 is for items the user may set as being
important (a meeting for example) which they wish to appear early. Items with
priority 1 may also be dynamically assigned a priority of 2 once a set period of time
before the item’s occurrence has been reached. This will push an item of important
from its chronological place to the top of the queue for the user to see.
I believe a typical day will likely have five to ten events occurring, whereas a busy day may
have up to twenty. These numbers are estimates based on my own day, so these numbers
may be subject to change.
I will have to make the table to display the upcoming five items at least. This seems like a
reasonable number, and will allow the user to look ahead at events in the near future
without being overloaded with less relevant events from later on in the day. If they wish to
see these later events, they can open the timetable to view them directly (although if I use a
linked list queue, the table size restriction is not an issue). The agenda table is, however,
designed to give the user a quick glance at their upcoming schedule; the implication is that
these upcoming events will be limited to the near future.
Design
Below is a flow chart detailing how items are added or removed from the queue.
The queue is created by defining an array of set length. Two pointers are included, the front
pointer for the first item in the queue, and the rear pointer to point to the next free space.
To add an item (enqueue), first, a node is created (by creating an instance of the node class).
The program then checks if there is an item in the queue. If there is, the program moves
onwards until it reaches the end of the queue. Once it reaches the end, the node is added.
To remove an item (dequeue), a check is carried to see if there are any items to remove. If
so, a check is made to see if there is only one item. Where there is only one item, the head
is set to nil. If there are more items, the head is set to the next item in the queue.
In a linked list, the nodes connect between each other, one following another. I will create a
class for the node, which the linked list class will call.
Below is an outline of the class diagram.
I will test the queue in playgrounds, and begin with setting up the linked list’s node class.
public class Queue<T> { The node class is referred to as Node throughout using a
typealias Node = LinkedListNode<T>
typealias, to make writing and reading the code easier.
var head: Node! The variables are set up. A queue needs a head, a pointer to
public var isEmpty: Bool { return head == nil }
var first: Node? { return head }
the first item (set as the head), and a pointer to the last
var last: Node? { item.
if var node = self.head {
while case let next? = node.next {
The last item will begin as the head. If there is an item, then
node = next the pointer points to this item. It does this by moving to the
}
return node next node and checking the node after. If that node’s value
} else { is not nil (i.e. there is a node following) the rear pointer
return nil
} moves to the next item, until it reaches the end.
}
There is also a public Boolean to check if the queue is
empty.
func enqueue(key: T) { The function takes data (T) and creates a node with
let nextItem = Node(data: key) this data. It then finds the last node in the queue,
if let lastNode = last {
lastNode.next = nextItem and sets the next item after that as the node just
} else {
head = nextItem
created.
}
} However, if there are no items, then the node
created is set as the head.
Evaluation
The queue was fairly simple to program, with the only issue being the error I made when
returning data.
The queue is now ready to be implemented.
Colour Coding
Overview
The three priority levels will use colour coding to distinguish them. Level 1 priorities will
have a neutral tone, since they will be the most frequently occurring. To make individual
Design
There is much around the events that has not been setup yet. I do not know exactly how the
priority will be stored or accessed, however I can create a general function that will setup
the cell.
When configureCell is called, the event and all its data is passed through as “event”.
The event’s name is retrieved, and titleLabel.text is set as the name.
The time of the event’s occurrence is also retrieved, and the time until the event is to occur
is calculated, and set as timeLabel.text.
Finally, the colours are established. Swift has a tool named “switch” which allows for testing
various cases of a variable. Upon retrieving the priority, the priority is tested against the
possible cases (default, important, and urgent). I will also create a default case for when a
mistake happens. Errors are catered for in this way.
switch event.priority {
case “default”:
titleLabel.textColour = event.colour
timeLabel.textColour = event.colour
cell.backgroundColour = event.colour withAlpha(0.2)
case “important”:
titleLabel.textColour = black
timeLabel.textColour = black
cell.backgroundColour = yellow
case “urgent”:
titleLabel.textColour = white
timeLabel.textColour = white
cell.backgroundColour = red
default:
titleLabel.textColour = black
timeLabel.textColour = black
cell.backgroundColour = white
The code is a little repetitive within the switch statement. It can be cleaned up by using a
function to generate the colours.
titleLabel.textColour = text
timeLabel.textColour = text
cell.backgroundColour = back
The function takes two colours as parameters, “text” and “back”. The former is used to set
the colour of the text (title and time) which will always be the same in all cases. The latter
sets the background colour of the cell.
Swift uses a feature named UIColour, which has default colours like red, white, yellow and
black, along with custom colours by specifying the red, green and blue values (along with
alpha [opacity]). These values are CGFloat values, which can be treated as normal floats if
hardcoding the values.
The new colour setting code will look like the following:
switch event.priority {
...
case “important”:
...
So far, this is just a base that is not ready to be implemented, since there are too many
variables that are still theoretical. The plan has been created here, but real implementation
will happen after I have completed the events.
This feature is unfinished as it relies on another feature being completed. Whilst I could
attempt a solution right now, there I is a very high chance I will have to change many details.
Therefore, for the time being, I shall leave this until the events have been completed. By
doing so, I have a better framework to base on, and can test using real data too.
Evaluation
In this cycle, I have established and setup the core of the application. However, these
features, while creating a baseline, are far from completed. There is much more work
required to bring these to where I wish them to be.
However, I will be putting this work on hold until I have implemented other features in
other views, as the agenda relies heavily on events already having been created and can
therefore work with them.
Focus Group: My focus group were disappointed with the progress on the application so far.
I am coming to realise that the features I intended to include may have been too ambitious,
and I predict this trend to follow. While I wish to limit this as much as possible, I cannot say
for sure. The focus group as a whole were not all too impressed with an unfinished feature.
A couple have studied algorithms, and liked how I had created not only a linked list queue,
but having done so quite efficiently as well.
Overall, a disappointing finish.
Testing Table
Test Success Type Description Data Expected Outcome
No. Criteria
1 6.1.1 Function Notes created, edited and deleted. Notes Notes can be created, their contents
modified, and ultimately deleted
permanently.
2 6.1.1 Usability The user has no difficulty carrying out the Notes The user can identify which feature carries
functions necessary to interact with the notes. out which function and successfully interact
with the notes view without prompting.
3 6.1.1 Function Text formatting carried out within view Notes/ Text can be formatted by some method.
String
4 6.1.1 Sanity Uncommon text types are entered. Notes/ While the database may not support any
String string type, incompatible strings are
handled.
5 6.1.2 Function A large number of notes created. Notes The application allows many note’s creation,
and will only prevent the user from adding
more notes if there is a potential memory
issue.
6 2.2 Function All notes are viewed as their collection. This does Notes All notes are viewed, although not
not include deleted notes. necessarily at exactly the same time.
7 2.2 Function A single note is selected from the collection to be Notes The note selected is viewed, without
viewed. interference of any other notes.
Setup
The first stage will be to set up “view all notes”. This requires a few features, namely the
navigation bar and buttons, the search bar, and the notes themselves.
To begin with, the data structure for the Realm will be set up. The following are required for
a note:
I have also added a tableview controller within the storyboard, and linked a view controller
“ViewAllNotes” to it. I have also added an empty view and linked a view controller “Notes”
to it.
Within Notes, there is a text field and a text view, which are linked to the view controller as
IB Outlets, “titlefield” and “bodyField” respectively.
A constant, newNote is set as an instance of the NoteData object. The variable can be
manipulated, and then submitted to the Realm after relevant validation.
Once the user submits, the text from the view must be saved to the newNote variable.
import UIKit
class NotesCell: UITableViewCell {
Save Button
To submit a note, the save button must be pressed. This appears in the navigation bar of the
note view.
Once a user is happy with the text they have entered, they may tap the save button, which
will submit the data they have entered. However, before submission, the fields must be
validated to check that there is data within the fields, and that data is not the default text. If
the data is invalid, then the application will show an alert to the user to inform them of this.
The submit button then pushes the new note or the changes made to an existing note to
Realm, provided the data is valid. Finally, the user is returned to the previous view, as there
is no more they need to do with that note.
Here, an alert is presented if the validation checks return false. Otherwise, the note is added
to the data store (currently a dummy implementation).
The alert is created and set with a default value.
let alertController = UIAlertController(title: "title", message: "message",
preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style:
UIAlertAction.Style.default, handler: nil)
I have then added the message within the validation and presented the alert.
if (titleField.text == "" || bodyField.text == "") {
alertController.title = "Warning"
alertController.message = "Please leave no empty fields when saving"
self.present(alertController, animated: true, completion: nil)
if newNote{
note.id = currentTime
add note to Realm
With Realm, items are all added to the table using the same function, realm.add(Object).
However, if the note already exists, then the parameter “update” will be set to true, so that
the existing note is updated rather than added as a new note.
I have been having another issue with the primary key, which is actually more of a general
issue regarding the whole object. In testing, I received an error while trying to add note
objects to the Realm (specifically to see whether my data structure is correct).
Realm supports a great variety of data types, however I have been having difficulty with
Swift’s native types, name NSDate(), which is the way I intended to store times. (It is also
worth mentioning that I will likely have an issue with storing colours for the Agenda, but
addNoteSegue is simply a check to see whether the note is new or existing. It exists due to
the slight differences in how I have implemented my note segues.
A simple way to improve the efficiency of this is to have the current date/time stored as a
constant once the save button is pressed, and simply update it, rather than generating it
each time.
This example shows how I have used currentDateTime to update the age an id.
To test, multiple notes have been created an saved to the Realm. I called a print of the
database for that object, and it appears that the notes have been saved as objects to the
Realm successfully. (This was done from the Agenda view, proving that it is not affected by
being in the Notes view as a local instance.) This shows that notes save to the Realm
successfully.
I performed three validation checks when attempting to save a note. In each case some data
was missing (title, body, both). The validation check works successfully, as each time the
alert popped up, and no invalid notes were seen to have been saved in the Realm store at
any point.
I tested the usability with members of my focus group. Overall, the interactions indicted
that I would have positive results with my target demographic. I noticed one user
repeatedly cancelled notes, not realising that they were not automatically saved.
I may make the save button stand out more aesthetically so users are aware that it must be
pressed to save a note.
Aesthetics
The notes are currently displayed like this (right) as there has
been no modification to the aesthetics of the prototype.
There are some modifications I can make within Swift to
make the view more pleasant, not only to the eyes, but to
work within too. For example, resigning the keyboard from
the fields when the user has finished typing, or opening it up
automatically are small modifications that can be made to make the user experience better.
To begin with, to be able to work with the text field and text view, the Notes class will
subclass the delegates to these two features.
A simple example function within Swift closes the keyboard when the user taps the ‘return’
button. If the user taps the return button on the title, the keyboard will close so they may
edit the body. This feature will not be implemented with the body since the text there may
be multiline.
The text field looks “very plain”in its default state (according to a member of my focus
group). A way to improve the aesthetics is to have a seamless transition from the title to the
text view.
This can be achieved by setting the background colour to white, removing the borders, and
giving the field a hard, coloured shadow.
Since this only applied to text fields, I have decided to place it within an extension, where I
can reuse it later if need be.
borderStyle = .none
layer.backgroundColor = UIColor.white.cgColor
I have not found a way to have placeholder text within a text view (like with a text field), so I
have decided to manually implement one. This can apply to the text field too, since
placeholder text is very faint.
The placeholder text works by having a default message displayed, which clears once the
user taps on the field to type in data. My function will work by checking to see if the text in
the field (or view) is default, and if so, remove it and allow the user to begin editing an
empty space. I could leave the default text, but in experimentation, I found it frustrating to
constantly clear the fields if adding many notes.
If the text is equal to the default text (in the case of the text
field, “Title”), then the program will replace it with an empty
string.
The user can then continue editing.
alertController.title = "Warning"
alertController.message = "Please leave no empty fields when saving"
self.present(alertController, animated: true, completion: nil)
}
else if (bodyField.text == "Note Description" || titleField.text ==
"Title"){
alertController.title = "Warning"
alertController.message = "Please add your own text or cancel"
self.present(alertController, animated: true, completion: nil)
}
else{
submission...
}
Testing consisted of usability testing, where the fields were tapped to begin editing to see
whether the text disappeared. When it did, that test was passed successfully.
I also tested saving the note with the default text, and the alert successfully shows up.
Focus Group: A member mentioned that having the text disappear as it does was quite neat,
however what if the user wishes to save a note with a title of “Title”. I do not believe that
the user is at all restricted by having a total of three words being prevented from use. I
decide to keep the checks in place.
The same member also mentioned how, while the feature was nice, the delay before typing
could begin was frustrating. When asked whether it was possible to reduce the delay, and
for what reason it was there, I could only respond that it was due to hardware limitations of
the device running the simulator. It was running many applications alongside, and did not
To work with this format, it must be converted into an array. This is possible through the use
of a map function with a given closure, since it returns an array. $0 can be used to represent
each item in the collection sequentially, so as the function iterates over the collection, each
item is appended to an array.
I can imagine reusing this function, therefore by creating this as an extension, it can be used
easily across multiple views to operate on the collection type (which according to the Realm
documentation, is a “Results” type).
extention Results {
This pseudocode outlines the basic
func toArray(array) { format for the code I will be writing,
converted = array.map{$0}
however there are a few nuances with
Swift to be aware of.
return converted
The return type for a function must be
}
defined, while also “self” can be used to
} operate on the item without having to
pass parameters. Therefore, I can also operate directly on
Therefore, the Swift implementation may look more like this:
This can then be used to populate the table. numberOfSections defaults to 1, whereas
numberOfRows will equal allNotes.count (the number of items within allNotes i.e. the
number of notes stored in the database).
The table can then be populated by working through allNotes array sequentially, and
applying the title and body to the cell. Since the notes in the array are still NoteData objects,
they retain all their information.
Firstly, I will have to make changes to the configureCell function, since currently it is based
around a different data structure.
The modifications are simple however. All that is required is modify how the data is
accessed.
The differences stem from passing a NoteData object into the function, as opposed to an
array of strings (each array representing one note, rather than the collection of notes).
Due to the different object passed into the function, the way the data is handled changes.
Array indexes are not accessed like before. Instead, the object states values are directly
accessed.
I have given the two segues from View All Notes identifiers, “showNote” and “addNote”.
That way, the two segues and their purposes can be differentiated.
Testing
The cells were tapped, and the indexPath.row printed. When tapping the third cell, the
printout was 2. This is exactly as expected, as the indexPath is indexed at 0.
The correct note data was then printed in the next view.
While navigating between the two views, an interesting thing happened – the Notes view
appears to move in over View All Notes twice. After some research, I believe the reason to
be that it is being triggered twice, and therefore is segued to twice.
The two triggers would be the segue within the storyboard, and the segue trigger within the
“didSelectRowAt” function. This means, to prevent the segue occurring twice, one of the
triggers must be eliminated. While I may work with an entirely code based segue, it would
require a vast amount of restructuring around the one segue, since my application is heavily
storyboard based. It also helps that the segues within the storyboard show a clear
If placed within an if let statement, the code can be protected from issues where the user
taps any cells below those generated (i.e. those without indexPaths). Once this is set to a
variable (say, “indexPath”), then the array can be accessed directly by the following:
if let indexPath = self.tableView.indexPathForSelectedRow {
tappedId = (allNotes[indexPath.row] as AnyObject).id
}
Swift requires the note be cast as “AnyObject”, which I am using until I finalise my object
type.
With this, the ID is saved into tappedId and is sent to the next view.
Note Search
An important feature for the user is the ability to search for notes. This is especially useful
should the user have saved many notes, since it will allow the user to easily find the notes.
This comes with the obvious prerequisite that the user remembers some content of their
note. I wish to implement this feature in such a way that the user need only remember a
snippet of either the body or title, and still have relevant results returned. This method may
have two drawbacks: the first is that the user may be returned with a large number of
results, but that may be a product of having similar text in many notes (for example naming
notes “note 1”, “note 2” and so forth, and then searching “note”). Unfortunately, this relies
on the user either using some other way to distinguish notes, or the use of advanced search
features outside the scope of this application. The second potential issue is where the user
cannot quite remember the precise text they used in the note they are searching for, and
therefore will not be returned any relevant notes. Again, this relies on the user’s personal
use of the notes and their own recall. Without the use of an advanced search method, with
more available options, this may be a difficulty for the user. For this application, an
advanced search on top of a secondary feature is too much complexity outside the aim of
the application, and therefore I will only focus on implementing a basic text-matching
search. There is the possibility of including this as a potential improvement in the future;
however, it is hardly a priority.
func updateSearchResults...{
if search bar is not empty{
text = text in search bar
filteredNotes = allNotes.filter{ note contains(text) }
}
else{
filteredNotes = allNotes
}
To update the table, the reloadData() function must be called on the tableview. This will
show the changes made to the array to the user immediately, as the table is repopulated
from the modified array.
I have decided in my implementation, I can increase the efficiency of the function by using
an if let statement for the search term, along with a check for whether the bar is empty on
the same line.
My Swift implementation will look like the following:
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text, !searchText.isEmpty {
filteredNotes = allNotes.filter { individual in
return (individual.body.contains(searchText) || individual.title.contains(searchText))
}
} else {
filteredNotes = allNotes
}
tableView.reloadData()
}
The filter function works in this case by checking “individual” (every note in the array being
filtered [allNotes]) against the parameters, which in this case checks whether any text in the
note body or title contain the search term (searchTerm).
in the case of the body (and repeated for the title too).
There is another consideration to take into account. In the case that filteredNotes is empty,
the numberOfRowsInSection function has to be protected against the lack of cells to display.
The issue is that filtered notes may vary in size quite rapidly, and when suddenly reduced to
0, the function may not be able to handle it.
For this situation, a guard statement will work perfectly. A constant, ‘notes’, will store
filteredNotes as an optional (giving it the possibility to return null). In the case that
filteredNotes is indeed null, the function will return 0, else it will return the length of notes.
Note Sorting
Being able to reorder a list of items as needed is always a useful feature. In the case of the
notes, having at least a chronological and alphabetical sort would be ideal. The user may
also benefit from their own custom sort (where they may reorder items as they see fit),
however I fear this may be very difficult to implement within the constraints of my table
data structure. Regardless, the first two sorts should still be doable.
Quicksort
I will use a quicksort on the note titles, since I believe the comparisons between strings are
made more easily in the way quicksort handles them.
While a quicksort may include an advanced way of finding an ideal pivot, in my
implementation it will simply work off a random index (in this case, the middle index of the
array). Although having a way to find an ideal pivot is beneficial, there likely will not be
enough notes saved by a user to make a real difference to the sorting and displaying time,
therefore I believe it would be an unnecessary feature.
The quicksort works by comparing every item in the array to the pivot. Larger items are
sorted into one array, and smaller items sorted into another. Values equal to the pivot are
if array.count > 1{
pivot = array[middle value]
This is a compact, barebones quicksort. It only carries out the basic necessities, but that is all
that is required.
Merge Sort
I will apply a merge sort to the note ages to sort them chronologically. The merge sort works
in two parts – the splitter and the merger. Merge sort works by initially splitting the problem
down to its smallest possible, and then merging the list back together by comparing items in
the sub lists.
My merge sort will work on this basis. The splitting part will be contained within the main
function call. The array is essentially sliced in half by finding the middle index, and creating
two sub arrays, which contain values that appear before the split index and values that
appear after. The split will not necessarily be even as the initial array may have an uneven
number of elements. This main function call, which does the splitting, is recursively called,
so the initial array is split into many sub arrays, all eventually of size one. The exit condition,
therefore, is when the array length is one.
After that, the arrays can begin to merge. This will be done through the use of a second
function. It takes two arrays, and compares every value in them. Values are populated to a
new array (the merged array) in order of size, where the smallest value is copied across
from the arrays by comparing the current values before moving to the next.
func mergeSort(array){
if array.count == 1{ return array }
else{
split = array.count/2
left = mergeSort(array sublist from index first to split)
right = mergeSort(array sublist from index split to last)
array = []
while left AND right have elements{
if current value in left > in right{
array.append(value in right)
}else if right > left{
array.append(value in left)
}else{
array.append(value in right)
array.append(value in left)
}
move to next value
My pseudocode for merging lacks some detail, and therefore cannot be immediately
converted into Swift.
For one, some method must be used to identify the elements in the arrays being compared,
and move after them. I could use a system of pointers, where the pointers mark the indexes
being compared, and move through the list. The pointer will always point at the smallest
item in array that has not yet been added to the merged array.
Here is the merging function once again, but as a flowchart including the pointers:
There is a problem with this, however. The algorithm ends before there is an “index out of
range” error, however this does mean there may be trailing items in the other array if they
are not equal.
To fix this, the algorithm must be amended slightly. The above algorithm finishes once a
pointer exceeds the array length (i.e. it point to an index out of range of the array).
However, this means for certain that the array length is less than the pointer, for at least
one of the arrays. Therefore, all that needs to be done is append all the items in the other
array to the merged array, and then both arrays have been fully merged.
This is quite simple to achieve:
All that needs to happen is have this addition appear at the end of the merge function, and
have it apply to both arrays (by using the relevant variable names etc.)
Since it uses a while loop, only the one with outstanding elements will trigger, while the
other already does not meet the condition.
I programmed the sorts with the specific data type it is handling in mind, which meant I had
to make some changes at the implementation stage to get it working with my system. Since
then, I have decided to change the structure of the sorts, especially since it is unlikely I will
have to reuse these sorts anywhere, which means I can tailor the sorts specifically to the
note object.
Therefore, it may be better now to sort the notes directly, rather than using references as I
did, as the age and title states can be accessed directly.
Focus Group: An important piece of feedback I received was that the order of chronological
note sorting was in the reverse of what they expected. It appeared that the opinion was
shared among the group, although I had not noticed myself.
Taking this into account, I applied Swift’s reverse function on the final sorted array, just so
the output matches what they were expecting.
While usability testing, users struggled at first to locate the function to delete the notes.
However, having said that, none of the user had to ask for direction and all eventually found
it within a reasonable period of time.
While maybe not the most intuitive part of the application, I believe it may stay as it is and
see no reason to modify how it works.
Displaying Notes
Now all features for View All Notes have been completed, I will focus on the final parts of
the Notes view, namely displaying a note selected, and saving an edited note.
When a user taps the note they wish to view, the primary key for the note will be sent
across. This will then be used to access the note from the Realm, which can then be directly
worked upon. The reason I take this approach is because, while I could send the entire note
across (having access to the data), to update it within the Realm, I will have to retrieve it
regardless. In either case, I will end up retrieving the note from the Realm using the primary
key; therefore, it is simply more efficient to do so from the beginning.
To display a note, the primary key must be sent across so that the note may be retrieved
from the Realm. Since the code for the segue check is already in place, all that needs to be
sent across is the Boolean value so the check can be performed.
With these two items, the note can be displayed. The view controller, having made the
check that the note to be displayed exists, will retrieve the note from the Realm, using the
primary key supplied. The data held within the note can then be displayed to the fields.
The code for the display itself is relatively simple, since it only consists of a Boolean check to
trigger the retrieval and display.
if addNoteSegue == false{
currentNote = realm.object(ofType: NoteData.self, forPrimaryKey: noteId)!
titleField.text = currentNote.title
bodyField.text = currentNote.body
}
if segue.identifier == "showNote"{
if let indexPath = self.tableView.indexPathForSelectedRow {
tappedId = (allNotes[indexPath.row] as AnyObject).id
}
let destinationVC = segue.destination as! Notes
destinationVC.addNoteSegue = false
destinationVC.noteId = tappedId
}
else if segue.identifier == "addNote"{
No action will be taken as a result of test 3’s failure, as I do not intend to implement any
formatting currently.
Altogether, I feel like this cycle went well. The constituent parts to create the entire feature
was longer than I had expected, however I believe my implementation is fairly concise and
offers adequate features.
While there may not be any glaring issues, there are some improvements that could be
made to tweak the feature to be better.
One such issue is the quicksort algorithm I used. The pointer is essentially selected
randomly, meaning that the algorithm may not be efficiently used.
A bigger potential issue may lie in sorting each time, and loading the view. Each time, the
array is regenerated, which may cause issues if there are enough items in the Realm. I was
unable to test this however, as it requires a large number of notes. A way to prevent this is
to store the sorted notes in a separate array, and load that rather than resorting the list,
provided there have been no changes. The issue is in that however, as that adds another
layer of complexity to the application, and another possibility for bugs to be introduced.
Related to view the notes list, my search function is fairly basic. It certainly gets the job
done, and is far better than not having one (to the point it may even be necessary), however
many applications that include notes also make use of an advanced note search, where the
user can filter the notes for better results. Given the sparse amount of details retained
about the notes, along with space constraints and the added complexity, I decided not to
include this. It is a possible improvement that could be made at a later date.
Focus group: after the unfortunate disappointing end to the last cycle, my focus group were
quite pleased to have something to play around with now. I did receive a few comments on
whether “that was [all]” to the application, but I assured them there was much more to
include. Relatedly, I was asked if more would be added to the view, in terms of features and
functionality. A good question, certainly, but not one I could concretely answer. While the
Schedule
After conducting some research, it appears there is no native way to display a ‘schedule’ of
sorts. The closest appears to be calendar, however there is little information on how to
create one, as opposed to integrating with Apple’s provided calendar application.
Therefore, I will have to create this myself. I potential possibility is by using a spreadsheet
and populating cells as events. I do not see any alternatives.
Going by a spreadsheet, the columns would show the days of the week, while the rows will
show the hours. The reason it is ordered this way is that the user will be able to view all
events as they are listed down the hours on any given day easily (as their phone’s default
orientation is portrait).
Unfortunately, I am unable to elaborate any further, as I do not know the best way to
implement this feature without much experimentation to see what methods will produce
the best result.
It is likely a greater portion of the planning stages will be incorporated into the
development.
In any case, the schedule must display the dates, days, times, and events. Furthermore, the
view will include controls for the events, so a button to add new events and a way of
opening the editing screen for views will be included.
The times can be held in an array, as can the days and dates, since they will be loaded and
displayed in a specific order (chronological).
The editing button will have to include variables to hold data about the event that has been
selected so that the edit view may be able to display the event.
Events
Each event will work on the same basis.
Event Creation
The user will create them with four required bits of information: the title, when the event
occurs, its display colour, and whether the event repeats or not.
Once the user has added the parameters they wish, they will tap the create event button,
which will save the event to the Realm, and will then be populated to the Schedule.
Event Viewing/Modification
After a user has created an event, they may wish to view the data regarding the event. This
applies especially where the user wishes to see extra data stored about the event, and all
times that the event will occur.
To prevent accidental edits, the event viewing may be locked behind an edit button, where
the fields cannot be interacted with unless the user taps the edit button. This is especially
important where the user may accidentally delete an event. The delete button only appears
when the user is viewing an event they have created (it makes no sense to delete an event
that has not be created yet). The edit button will also change to say save once the user
begins editing.
The edit/save button only saves changes within the view. To commit the changes, the
update event button will be pressed (which replaces create event).
Repeat Switch
Events come in two flavours, repeating and single. A repeating event is static on the
schedule and never disappears (unless modified by the user). A single event appears only
once, with a predetermined time, day and week of appearance.
This integrates the functionality of a calendar within the schedule.
Whether an event repeats or not is defined by the switch, where changing the setting also
changes how the event occurrence picker displays to the user.
With a repeating event, the user will select all the times for the days that an event will
appear.
For a single event, only a single picker will display, with a time and date selection.
The chart displays how the selection may work with view. Occurrence picker will likely be a
button, and therefore the next view displaying the relevant picker type.
The first format has the advantage that the user can pick any colour they wish. Should many
of their events be associated with shades of brown for any reason, they may pick those
varying shades at their leisure, as all colours that may be displayed by their device are
available to choose from. With the latter example, this is more restricted.
Occurrences
Two version of occurrences may exist. The former caters to repeating events, the latter to
single events.
I believe the occurrence selection for the latter can easily be done with a picker view, where
the possible selections are the time and the date of occurrence.
The days and dates displayed at the top (2 rows) are frozen, along with the column for the
hours (1 column). This allows the user to scroll through, and where they may have left those
rows and columns behind, they are instead still attached to the screen edge and follow the
user, so they can always view them as a reference.
I wish to have the actual dates displayed, so I will add those to the top. This will replace the
hardcoded string already in place.
The first day of the week for a given week must be found. I am unable to do this, however
StackOverflow user Ram’s code (https://stackoverflow.com/questions/46402684/how-to-
get-start-and-end-of-the-week-in-swift) achieves this.
This makes use of the startOfWeek function immediately. This is then converted to a string
using:
let mondayString = formatter.string(from: monday!)
A function is then to be created to populate the rest of the date to the array.
To test, I will run the code. I will print monday and mondayString. The two should match in
date, while mondayString should be formatted, and each should be equivalent to the date
of the last Monday.
The array result will be printed, and should appear formatted. The first value should equal
mondayString, while the subsequent values should all be an increment of one to the value
of the first item in the array.
Event Framework
I will begin with repeating event frameworking, as it is the basis of the application. I will
focus on single events afterwards.
The event data model is created in the realm with the following parameters:
Name - string
Colour – string/colour type
Occurrences - array
Description - string
Priority - integer
ID – string
Name – string
Colour – string/colour type
Occurrences – array
Description – string
Priority – integer
ID – string
I will set the ID as the title. The name can be set as anything the user may wish. With that
comes the obvious issue of two events with the same name, and being able to differentiate
them. By setting the ID as the name, and using the ID as the way of pulling and updating
events, the user is restricted to naming only one set of events a particular name. (This only
matches the exact string. For example, “ABc” is not equivalent to “abc”.)
By using the name as the ID, conflict handling is instantly managed. Should the user type the
name of an existing event, the application will treat the event being created as an edit of the
existing event. The user may in fact wish to use the add event button as a way of editing
events (for example, they consider adding a separate event, but decide instead to merge it
with an existing event; all they must do is type the name of the existing event and make the
changes they wish).
With the model created, I will now work on the view and view controller.
The view is created with a title label, a text view for extra information, a switch to decide
whether the event is repeating or not, and a priority picker. The buttons are then setup – a
button to set notifications and alarms, a button to choose the colour, a button to choose the
//MARK: Variables
var repeatingEvent = RepeatingEvent()
var singleEvent = SingleEvent()
The methods below have been created to handle the display elements of the view.
Currently, a simple description of their use has been included. The methods lay out the
structure for the rest of the view.
@IBAction func occurenceButtonPressed(_ sender: UIButton){
Navigate to occurrences view
}
//MARK: Text Setup. The function below carries out the same placeholder functionality
implemented within the Note view. It has simply been adapted for this view
func textViewDidBeginEditing(_ textView: UITextView) {
if (textView.text == "Extra Information"){
textView.text = ""
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if (textField.text == "Event Name"){
textField.text = ""
}
}
}
if segue.identifier == "editEventSegue"{
let destinationVC = segue.destination as! EventVC
destinationVC.eventName = name //send event name
}
Data is only sent over when an event is being edited (and therefore the edit segue triggers).
name is the variable to hold the primary key of the event. Whether this will in fact be the
title of the event is to be decided.
if segue.identifier == "occurenceSegue"{
//send over occurence data
let destinationVC = segue.destination as! OccurencesTVC
destinationVC.occurences = occurences
}
else if segue.identifier == "colourPickerSegue"{
let destinationVC = segue.destination as! ColourPickerViewController
destinationVC.colour = colour
}
}
Of course, this is just framework setup. The data to be sent has not be established yet. A
default value will be set so the program works while the variables are in their prototype
stages.
Again as with notes, the same view will be used when creating a new event or editing an
existing one. There is some complexity added however, as the edit event screen may change
quite a bit depending on the user’s choices.
First of all, when adding a new event, no event identifier can be sent (as there is no event).
This means that the identifier (which will likely be a string), will be sent as empty.
If the event view receives an empty identifier, then it knows that a new event is being
added. It will then carry out the relevant setup to present the correct view to the user.
As can be seen, the steps a fairly similar. This makes sense, as adding and editing an event
both work on the same fields. However, the crucial difference is how data loads. In the case
of a new event, there is no data to load; therefore, nothing should load into the fields
besides the placeholder text.
The two views will also have some cosmetic and functional differences. The delete button,
for example, will not show for new events, as there is nothing to delete.
if eventName == "" {
interaction = true //enable interaction with all features
deleteButton.isHidden = true //hide the delete button
occurenceButton.text = "Add Occurences (Day/Time)”
submitButton.text = "Create Event"
}
else{
repeatSwitch.isHidden = true //cannot change event from single to repeating
switchLabel.isHidden = true
The basic framework for the view is created. The else statement in fact must be split in two,
as there are two possibilities for when an event is to be displayed: the event is repeating, or
the event occurs once. For either of these cases, the basic display of the view is the same.
The differences lie in the appearance of the event in the schedule, the underlying code, and
how the occurrences picker will work.
Colour Picker
The user can navigate to the colour picker from the event view by tapping the colour picker
button.
The button takes them to the currently empty view.
I have decided to make user of MrMatthias’ Swift Color Picker
(https://github.com/MrMatthias/SwiftColorPicker) to achieve the colour picker.
The installation is relatively simple. The files are imported to the project, which includes a
view controller setup class.
I will simply make my modifications to this class regarding my implementation of the colour
picker.
I have also added some of my own variables.
colour (optional string) stores the colour sent from the last view. In the case of a new event,
no colour can be sent.
As in that case it will be empty, a simple check can be made as to what colour to display.
if colour == nil{
pickerController?.color = UIColor.red
}
else{
I have also added a submit button to the view, as the user will have to navigate back to the
previous view.
It will send the colour generated by the picker (currently represented as a string in “colour”),
while popping itself from the navigation stack.
The stack is obtained with let stack = self.navigationController?.viewControllers
The previous view is then found, and cast as the view it is: let previousView =
stack![stack!.count - 2] as! EventVC
The colour selected is sent to the previous. This is accessing the same variable used to send
the colour to the colour picker. previousView.colour = colour
Finally, the view is popped. self.navigationController?.popViewController(animated: true)
This is tested by performing the actions. In my tests, this worked correctly. However, as no
colour is being sent, the colour sent and received is always nil.
With the colour picker installed, the user may select a colour by tapping a colour or dragging
through the colours. The display at the top is very small however, and also does not
represent how events will be displayed within the schedule.
Event display within the schedule is an important consideration to make. The plan is to have
the cell of the event coloured as the colour the user picks. The default text colour is black. If
the user picks a dark colour however, they may have difficulty distinguishing the text from
the rest of the cell. A possibility to interpret the colours exists, where if the values are below
a certain value, the text colour is changed. However, this will require extensive testing to
find the right colour at which to change the text colour, and may not even be possibly to
carry out if the colour to change the text at is different per hue. Furthermore, what I
consider an appropriate value to make the text change may not be the same as any of my
potential users. This is not a viable route to take due to the large uncertainty associated
with the success of the method.
Alternatively, I could display events where the text colour is the colour selected, and the
background a faded version of that same colour. This allows all dark colours, but introduces
issues where the colour is near white. The range provided by this method is greater
(assuming that the unsupported colours or prevented from use), and is far simpler to
implement. Changing the text to black for light colours carries the same issues as the
method described above. Instead, I will display, within the colour picker view, a label, which
will be colours exactly as the cells in events may be.
As the user selects colours, the label will be updated. The text will be the hexadecimal string
(which has the advantage of allowing the user to use a hex string as a reference for a colour
they may wish to select), while the background is the colour, but faded. This way, the user
sees how the event will be displayed immediately. While light colours will not be restricted
from selection, the user will be able to see themselves it is impractical. However, they may
select it if they wish.
As can be seen with the last option, the text is very faint. The user is made aware of this as
they can see the example before they submit their selection.
Before this function can be implemented, the UIColor must be converted into hexadecimal
for display. In addditon, UIColor must be converted into a compatible form to save the
Realm, which could be by using a hexadecimal string.
The translation must work in two ways, UIColor to hexadecimal, and hexadecimal to
UIColor.
UIColor takes red, green, blue and alpha values. The values taken are between 0 and 1,
where the greater the number, the greater the saturation of that colour. Hexadecimal
colours are represented by a 6 digit hexadecimal number, where each two subsequent digits
represents red, green and then blue. There is no alpha representation, however I do not
believe this is an issue as I have no intention of using the alpha value.
59 in hexadecimal is 3B. To conversion will be into a string, which can be done using a
formatter while performing a string conversion.
The formatter applies with the string conversion. The string function takes two arguments,
the format and the number. The number is input as above, using the one line integer
conversion. The number is converted to a hexadecimal string using the format “%X”.
However, this requires padding, in case the value does not have two digits (16/F or less).
This is because every colour is represented by 2 digits in hexadecimal, and any number less
than 16/F must contain padding. This is supported within the format. To do so, two things
are required: the padding, and the digit length required. In my case, I wish the number to
remain unchanged, so the padding will equal nothing, and is therefore represented by 0. As
each number has to be two digits, this is the value to enter.
The format is finalised as “%02X”.
var r: Double = 0
var g: Double = 0
var b: Double = 0
String(
format: "%02X%02X%02X",
Int(r * 255),
Int(g * 255),
Int(b * 255)
)
Now, the values of r, g and b have to be obtained from the UIColor object.
As can be seen, UIColor takes CGFloat values, so I will change my variable types from
Doubles to CGFloats. The differences are minor for my use, so that change is
inconsequential.
Imagining that “col” is the UIColor type returned from the colour selection, the values of
red, green, and blue must be extracted.
This can be done using the “getRed” function, which returns CGFloats per colour value
(including alpha). I will pass the variables in as a reference, since they have been prefined.
The alpha value is also returned, which will be stored. This is in case I wish to use it in the
future.
var a: CGFloat = 0
To finally implement this to operate on UIColor types, I will create an extension, so it may
apply wherever.
This requires some tweaking of the syntax, but the final code is as follows:
extension UIColor {
var toHexString: String {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
return String(
format: "%02X%02X%02X",
Int(r * 255),
Int(g * 255),
Int(b * 255)
)
}
}
The tests have all passed. It can be assumed that if the default colours are being displayed
correctly, then the hex output is correct for all. It is also helpful that the simulator shows the
colour being described.
The conversion must also be carried out in reverse, so a hex colour may be used by Swift.
The hex colour can be stored in Realm as a string, however the views where the colour is
displayed cannot use this, hence the need for the convertor working in reverse.
Unfortunately, the conversion in reverse is simply a case of doing the same steps as before
in reverse, as will be detailed.
To begin with, I will also make this feature an extension to UIColor. That way, the convertor
can be called anywhere, as with the convertor above.
}
The hex string passed through is referred to as hex within the function (also, as the function
operates on strings only, this has been specified as the object type of the passed variable).
With the setup complete, I will now plan the algorithm.
The steps are as follows:
1. Convert the string into a hexadecimal number
2. Extract the red, green, and blue values
3. Convert into the right format (CGFloat, 0-1)
4. Populate values to the default function
The first step is conversion. This can be done using a scanner, a very powerful tool available
within Swift to convert string objects into numbers of any sort. While it does not do straight
conversions to CGFloats, this is no matter as the hexadecimal number needs to be split into
its constituents.
Regarding splitting the number, I have decided not to simply split the string into every two
characters. The reason is that this approach does not scale well should I change my data
structures or usage.
By making a number conversion first, and then retrieving the numbers through
mathematical operations will allow for better scalability, as will be seen in the usage below.
Before that however, the number conversion is carried out. A variable (scanner) will be set
as the string (hex) passed into the Scanner function.
let scanner = Scanner(string: hex)
A variable to hold the result of the scan is then initialised. This will be a hexadecimal integer,
however as it is 6 digits (potentially representing up to 16777215), this will be a UInt64
(allowing 64 bit representation of the integer so no data is lost). This is set to zero upon
initialisation.
The scanner will then operate on the string, scanning for a hexadecimal integer in long
presentation, and, using rgbValue as a reference, will return the string as an integer in
hexadecimal representation.
scanner.scanHexInt64(&rgbValue)
This function should, so far, take any hexadecimal string, and return a hexadecimal integer.
I will test to see that a given hexadecimal number is returned as an integer.
The value is converted to an integer when printed, however otherwise, this is correct.
The extraction of the red, green and blue values can be done quite cleverly using a bitmask
and a bitshift.
First, for each variable, a bitmask is performed. The bitmask is used to cover over the
irrelevant values. In the case of red, for example, only the first two digits are wanted. A
Boolean AND operation is performed against the number FF0000, which removes the green
and blue values. Likewise, the mask is FF00 [00FF00] and FF [0000FF] for green and blue
respectively. This leaves the two digits of the colour to be extracted.
In the case of red and blue, although the colour has been extracted, there is the issue that
the number remains very large. Each representation is on a scale of 0 – 255. Given pure red
(FF0000), with the bitmask performed, the result is FF0000. This is equivalent to 16711680.
This, of course, is not ideal. To reduce the value back down, it can simply be shifted by 16
bits. This shifts the bits into 8-bit representation. The lost bits do not matter, as they have
already been masked to 0. Another way of writing 8-bit binary is 2-digit hexadecimal.
Therefore, the result of applying the shift and mask on the colour results in red being given
As can be seen, 22 is returned for each value. This is correct, especially considering that 16
in hexadecimal equals 22 in denary (16 + 6).
The final step is to input these values into the UIColor function. The only thing to be aware
of is that it takes an alpha value, which I will have to supply. As I am not using any
transparency with my colours, I will force a permanent value on the alpha component (1).
The call is simply self.init(r, g, b, a), as it is within the extension of the UIColor object. At the
end of all the converting, the final step is essentially the same as performing the conversion
and split separately, and then entering the values into the object manually. This way
however allows just the string of the hex number to be passed in, with a result of a UIColor
object.
self.init(
red: CGFloat(r) / 0xff,
green: CGFloat(g) / 0xff,
blue: CGFloat(b) / 0xff,
alpha: 1
)
Of course, hexadecimal numbers cannot be passed in directly, meaning that the values must
be divided by 255 (represented in hexadecimal here, allowing the division operation to be
on the same types of number.
Here is the final function:
extension UIColor {
convenience init(hex: String) {
let scanner = Scanner(string: hex)
scanner.scanLocation = 0
scanner.scanHexInt64(&rgbValue)
self.init(
red: CGFloat(r) / 0xff,
green: CGFloat(g) / 0xff,
blue: CGFloat(b) / 0xff,
alpha: 1
)
}
}
To test, the extension will be used to convert the hexadecimal values obtained in the
UIColor -> Hex test to convert back into UIColors. The values should match up.
With the conversions complete, the data can now be handled in and between views, and
updated to the database.
The first point I will handle is displaying the colour to the label.
A simple label has been added to the view, with some minor modifications. First of all, it has
been constrained to the colour picker, so it moves with it if the screen size changes. The size
of the label has also been increased, which is because it has to accommodate text of a very
large font size.
The label is referenced by an outlet called label.
Within the function called whenever the colour is changed (pickerController?.onColorChange
= {(color, finished) in ...}), I will place code to push data to the label.
To test that the output was correct, I looked up the hexdecimal value output in a colour
finder, and then visually compared the shades shown. I believe the matches were identical.
With the selector and its display to the user completed, the next step is to handle segues.
This includes incoming and outgoing segues.
The framework for the segue is already in place. All that is required is specificity. The segue
will be sending relevant data back.
In addition, the setup function needs to be finalised, so an existing event’s colour is already
selected.
In the case of sending data back, the function is in fact completed. As the variable “colour” is
set with the hexadecimal string whenever the colour is changed, no further work is needed,
as the string is already sent back to the previous view.
Regarding incoming colours – in the case that no colour is sent (new event), then the label is
set to be the default colour, which is currently set to red. As with when the colour is
changed, the label text and background colours are set (on the same framework, but with
UIColor.red). The text is set to FF0000, the hexadecimal representation of red.
Where a colour is received, pickerController.color is set as UIColor(hex: colour), as this
returns the UIColor value for whatever the colour may be. The same setup on the label then
applies.
With all this in place, colour should be saveable now, as in the user may select a colour,
return to the previous view, and still be able to come back and view the colour change they
made.
This process will be demonstrated in the following test:
Initial colour selected and saved. As it displayed correctly, a second is picked. This also
displayed correctly.
The view is working correctly.
Evaluation: This view had some difficulties to implement, especially when it came to making
the initial decision as to how to make the view.
However, the colour picker Pod found was incredibly useful. It was also incredibly
convenient that it was precisely what I was looking for. The colour picking is incredibly
intuitive – it consists of only sliding the finger to select the colour desired.
I am also happy with how the label turned out, as it shows the user how the event will
appear very effectively.
I also thought that the hexadecimal convertor was very nicely implemented. It makes good
use of some unique functions within Swift, and performs a bit of impressive backend work.
Focus group: They were very impressed with how this view turned out. They spent a not-
insignificant period of time playing around with the picker, because it is quite an interesting
tool to work with. One user commented that if one slides through the colours quickly
enough, a visible “bumping” can be seen with the text on the label.
This is due to the text changing quickly as the hexadecimal is shown. The “bumping” comes
from the text not being monospaced. As the characters change, so does the spacing, and
therefore width of the text on the label, which gives rise to the moving effect.
This issue can be solved by using a monospaced typeface, which I may consider. This does
not heavily detract from the user experience however, and I also feel the font I have already
selected it quite appropriate.
Occurrences
The Occurrences view is important for the user to pick the times at which their event
appears within the Schedule.
As discussed previously, some approaches for this will not work very well. Raw input, for
example, will not be included, nor will multiple picker views.
The best solution I considered was using a tableview with multiple selectable cells. Each cell
appears within a section, representing a day.
The main features of this are:
hours will be used to populate each section. For the time being, it will be hardcoded in,
however once the times that can be selected is variable, so will this variable become truly
so.
occurrences will be generated from the selected cells within the sections. Given my
understanding of how indexPath works, each section has an index, and each cell has a
second index within that. That means that a selected cell has two location paths – the
section it resides in, and its own index.
The section pointer indicates the day (i.e. section 0 corresponds to Monday). This is easy to
work with. The difficulty comes with the index of the cell. Say the selected range is 9am to
12pm. The hours 9 and 10 are selected. This is indicated by index 0 and 1. This does not
intuitively line up. While I could work around it, for my own ease of use will introduce a
constant named timeDifference.
This will simply be the difference between the index and the first time (in this case, 9). In
fact, regardless of the first hour of the range, the difference between the hour and the value
of the first index is always going to be equal to the value of the hour, as the first index is
always 0.
A better name for the constant may be startTime. Regardless, what the constant allows is
for me to make sense of the values being worked with. Where the range could change, it is
not helpful when testing to be returned indexes that do not match up with the hours
displayed.
Therefore, within the viewcontroller, I will handle all the hours as their index +
timeDifference. This allows me to work hours that match up with the display.
self.tableView.allowsMultipleSelectionDuringEditing = true
This allows a UI checkmark and the cell to dark when selected. The specifics of this are
defined within the custom cell class.
This is created with the default functions and embedded so the cells are instances of it.
The cell class includes:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
This make the cells reusable (for procedural generation), and ensure they are not generated
pre-selected.
And:
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
}
This controls the selection of cells, where they can be selected and deselected, and upon
selected, display a checkmark.
The cell class also controls an outlet for a label within the cell. This outlet will simply be used
to write text to the label.
As the cells are generated per section by the tableview controller, they will be passed the
hour they are to represent. With all the hours within the hours array, the cell label will equal
the item at the index indexPath.row. This means that cell 0 is given the value at hours[0],
and cell 1 given hours[1] and so forth to the last cell.
This ends most of the setup necessary.
The tableview has been populated with some hours, and cells can be selected and
deselected. I have also included a submit button which has been linked up to the view
controller with an onPress function. This will work much the same way as the submit button
for the Picker View.
Event Submission
Event submission is handled when the user taps the submit button. The submit button will
change its appearance whether the event is being edited or added as a new event (however
this will be handled at a later stage).
I will create default alert to handle the case where the data the user enters is invalid. As I
am only using repeating events for the time being, I will not perform any validation on the
event type or anything like that. Instead, validation will simply be performed to check that
the user has input data into the correct fields, and that enough data exists to create an
event.
Name
Colour
Occurrences
Furthermore, to verify whether the checks have been passed, a variable name valid
(Boolean) will hold whether the tests have passed or not. If valid is equal to true, then the
validation has been a success.
While more stringent validation checks can be performed on the event, checks against these
three perform the least amount necessary to ensure that an event can display correctly, and
can be interacted with.
The validation checks will be to see whether the variables shown are in their default state,
and if any of them are, then the data will be rejected. The user will have to be informed of
the invalid data. This can be done using an alert, which at the very least informs them that
there is some invalid data. To add to that, the messages could be specified depending on
the specific form of invalid data.
let defaultAlert = UIAlertController(title: "Info", message: "", preferredStyle: .alert)
As this is only setup, the message is left empty, and no actions have been added to the alert.
The validation will work like this:
valid = false
This simply checks whether the data entered is empty, or in its default state.
The write to Realm statement will then occur within an if statement that checks whether
valid equals true or not. If it is true, then the event is created and submitted to the Realm.
Otherwise, an alert is presented to the user, like so:
if valid == true{
let successAlert = UIAlertController(title: "Info", message: "Schedule Updated", preferredStyle: .alert)
successAlert.addAction(UIAlertAction(title: "Return", style: .default, handler: { action in
self.navigationController?.popViewController(animated: true)
}))
Perform Realm update
self.present(successAlert, animated: true)
}
else{
defaultAlert.title = "Info"
defaultAlert.message = "Some fields may have invalid data \n Please try again"
self.present(defaultAlert, animated: true)
}
As can be seen, if the event is invalid (valid =/= true), the alert is presented with an error
message. Nothing else is done, as the view waits for the (hopefully) changed input.
If the data read is valid, then a different alert is presented, success alert. This contains one
button (labelled “return”) which essentially forces the user to return to the previous view
where they will find their newly created/modified event. The return is done by simply
popping the view from the navigation stack.
To test this, invalid data will be entered (no title, no colour, no occurrences), and the submit
button pressed. The alert should pop up with the message shown above, and no further
actions taken.
To test that the checks pass, valid data will be entered. Where I have written “Perform
Realm update”, the variables holding the valid data will be printed. If the data is printed,
then the checks passed. The printed data will be compared to the entered data.
Furthermore, the application should have moved back to the schedule at this point, even if
events are not being created yet.
For an event to be added to the Realm, its occurrences must be reinterpreted. Realm, to my
surprise, does not support native arrays. Rather, it uses its own custom type. This means I
must adapt my received data to this data model.
A day object is then created for Realm’s custom array type. The object Day has a constant
dayItem, which takes a List of Hours.
return day
hour.item = val
return hour
I will separate these two operations out into separate update methods that will be
called within the Realm write statement.
event.id = name
return event
}
event.name = name
event.week.removeAll()
The two functions are then called within the write statement, which adds the event to the
Realm database.
To test, I will be checking whether the events have submitted successfully. This can be done
by printing the entire object database and checking the object is in there correctly.
With new events, the event only needs to appear there. However, in the case of a modified
event, the old data must be compared to the output, which should only match the new data
(or, any data that was modified). If the data modifications made match the output data,
then the test was passed successfully.
Event Deletion
I will include the methods to delete an event here.
The delete itself is rather easy. It simply requires obtaining the object (which is received by
the view on load), and calling the delete function for Realm.
The user will then be directed back to the previous view.
There will be issues if a user attempts to delete an event that has not been created yet.
Therefore, the button will be hidden if the user is adding an event. This prevent the user
from being able to interact with the button and cause issues, and removes the need for
validating whether the event exists in the database or not. By virtue of only appearing when
the user is editing an event, only existing events can be deleted.
I will include the button hiding with a whole host of UI modifications made between when
the user is adding an event or modifying an event. For a new event, the delete button will
be hidden, whereas the submit event button will show “Create Event”. In the case of an
event being edited, the delete button will show, the repeat switch will be hidden, and the
submit event button will read “Update Event”.
This is all handed quite simply within viewDidLoad, where the check is performed as to
whether the event is new or not (eventName == “”, indicates a new event).
The buttons to be changed have their isHidden property set to true, whereas to change
button text, the button setTitle function is passed the string to change the text too.
An extension of this differing views feature is to set editing for editing events.
I will continue this in a new section, as it is important to ensure that the user does not
accidentally delete or modify data they did not intend to. If they are viewing an event, they
Edit Button
As described in the delete feature, I believe it a good idea to add a preventative step for the
user.
The edit button will be a navigation bar item. This will be linked up to a mod check, which on
every other press performs an action.
Contextually, that mean that the user will begin editing, and the elements will be unlocked.
The user, when done, will tap the button again, and it will perform the lock action. The user
may then tap the button again to perform the first action. This can continue indefinitely.
Within the buttonTapped function for the button, the check will be performed using an
incrementing integer. Every time it is a multiple of two (count % 2 == 0), the elements are
locked. Otherwise, the elements are unlocked.
Locking and unlocking will happen within a method within the class. As element locking in
Swift consists of simply setting the element isUserInteractionEnabled state to false, the
element cannot be interacted with.
Furthermore, to inform the user (non-verbally) that interaction is disabled, an alpha
component of 0.5 will be applied to locked elements, as this is universally recognised as
being an indicator of something a user cannot interact with. This is done by setting the alpha
state to 0.5 (and back to 1 when reversing).
The setter function looks like this:
func modifyInteraction(set: Bool){
//nameLabel.isUserInteractionEnabled = set //used as ID, therefore
cannot be changed
repeatSwitch.isUserInteractionEnabled = set
priorityPicker.isUserInteractionEnabled = set
descriptionLabel.isUserInteractionEnabled = set
colourPickerButton.isUserInteractionEnabled = set
occurenceButton.isUserInteractionEnabled = set
reminderButton.isUserInteractionEnabled = set
//submitButton.isUserInteractionEnabled = set //disabled after
user selects done
if set == false{
priorityPicker.alpha = 0.5
priorityLabel.alpha = 0.5
colourPickerButton.alpha = 0.5
occurenceButton.alpha = 0.5
reminderButton.alpha = 0.5
//submitButton.alpha = 0.5
}
else{
priorityPicker.alpha = 1
priorityLabel.alpha = 1
colourPickerButton.alpha = 1
occurenceButton.alpha = 1
}
The reason for separating it out as a method is because it is so large, however it simply
manages all the elements at once.
The edit button is then created with the modulus check:
override func setEditing(_ editing: Bool, animated: Bool){
super.setEditing(editing, animated: animated)
Event display n
For an event to display, all the event objects saved to the Realm database must be
processed and output to the schedule.
I may take a similar approach to converting the Realm objects into a usable structure as I did
with the notes.
Cells are procedurally populated. Each cell accessed and populated with data. The best way
to do this that I can think of is to have the name of an event found via its occurrence. From
there, the name can be used to access all the data about the event by calling it from the
database.
Each event’s occurrences will be translated from the Realm type back into a 2D array. From
there, the occurrences can be checked if any match the cell position. A collection will hold
all events, along with their occurences. As occurrences already appear as a 2D array, the
collection will either be a 3D array or dictionary of 2D arrays.
weekTimes = [[Int]]
dayTimes = []
for hour in day{
dayTimes.append(hour) //array of hours for given day
}
dict[event.name] = weekTimes
return dict
This code completes the job of working through every event and adding its occurrences to
the converted format, however it has the drawback of containing three nested ‘for’ loops.
The time complexity in the worst case could therefore reach O(n3). However, there is a
known maximum problem size. That is the case where all 24 hours are filled. This means
there are 24 hours, for 7 days for an event, giving 168 as a maximum for append operations.
Therefore, despite the algorithm looking inefficient on the surface (and it certainly would be
for a large problem size), the problem size is capped, preventing the inefficiency of the
algorithm to show through. 168 operations is well within the capabilities of any modern
mobile device, iPhone included. The operations can most likely be completed easily enough.
However, a time complexity issue may come into play where the events must be displayed.
Each cell has its column and row. This can be passed to a function that searches through the
dictionary and returns the item that contains the appropriate values in the occurrences.
For every cell in the schedule, it will have to search through the dictionary until the item is
found.
An interesting quirk shows up here. The worst case scenario for the schedule display could
be one of two cases:
To get the name of an event by occurrences, a linear search will be applies to the dictionary.
A binary search cannot be used, as the data needs to be sorted for this to happen. The data
is ordered by event, not time, and therefore cannot be searched by occurrence.
With a linear search, each item is checked to see whether it matches the cell position. If a
match is found, the event name is returned and the loop broken. The name can then be
used to send the event’s name and colour to the cell.
To obtain the event name first, the search is applied:
This algorithm checks through, for every event in the dictionary, the subarray of occurrences
that matches the column of the cell.
Every hour (row) within the column is then checked. If an hour match is found, the event
name is returned via the dictionary key.
While linear searches are an inherently inefficient way to search for data, that does not
mean that the they cannot be optimised. Here, for example, only the relevant column is
searched. That means that, for a given cell, at worst, 24 iterations are performed for a single
event. Assuming that there can only be a maximum of 7 events (due to all spaces being
taken up), the 168 iterations are performed to see that the cell is empty. However, as
discussed previously, this can never be the case, as for the cell to be empty, all spaces
cannot be taken up, so the search cannot perform at its worst.
On a related note, the loop will be given an identifier where, upon the event being found,
the loop will be broken. As the event has been found, no further searching is needed, and
anything more is unnecessary (especially considering it is impossible for another match to
be found).
While accessing the column and row in the data structure, some shifting has to be done, as
the column and row returned by the cell are not going to directly correspond with the
column and cell in the structure. This is due to the schedule including rows and columns for
the hours and date to display.
if hour == (row+startTime-2){
name = event.key
return name
To populate cells, the event name is found by the cell position by the method above. An edit
is made to customise the colour of the cell. Where the cell has a name (an event has been
created with its position), then the event is found in the array of all events.
Event Navigation
At this point, created events cannot be navigated to. A simple way to handle navigation is to
receive the event name by cell position once again.
If the column or row is greater than those that are locked (c0, r0 and r1), then the segue is
triggered.
Similarly, if the name found is empty, the segue will not trigger. This does not affect the add
button as it is not controller triggered.
if name != ""{ perform segue }
This checks the if there is no name, and if that validates to false (there is a name), the segue
is triggered.
Upon testing, the empty cells no longer trigger, however I had an issue with the locked cells.
The segue is being triggered strangely, and I cannot pin down the conditions for trigger.
Although I am not sure what the issue is, it appears the OR statement may be the issue.
I have changed it to an AND statement checking the same conditions:
if (indexPath.column > 0 && indexPath.row > 1){ trigger segue }
I am not sure why, but this appears to work properly now. None of the locked cells trigger
the segue.
Feature Test
Before beginning formal testing, I noticed a small bug where the events would not appear
until scrolling through the Schedule.
This is due to the cells immediately shown after navigation not having been generated with
the new data on view appearance. The setup code was contained with viewDidLoad and
reloaded on cell generation (as the view is moved and new reusable cells are created).
Evaluation
I have ended this cycle earlier than I may have initially intended. The reason for this is that it
was taking far longer to program than expected. I ended up spending a very significant
amount of time browsing through not only Swift and Realm documentation, but external
API documentation also. Searching for the most appropriate API for various tasks took far
longer than expected. However, this still does not come close to how long it may have taken
to program the features myself. The APIs and Pods have been incredibly useful, but have
been a time-sink nonetheless.
Collapsible occurrences cells, for example, was a feature I intended to implement. I did in
fact find examples of its implementation, and as it was a feature I very much wished to
implement, I spent a great deal of time attempting to understand how it worked so I could
put it in myself.
In the end, I was unable to, and the feature was dropped. The time spent searching for,
experimenting with and attempting to understand the feature was not recovered however.
Although that is one specific example, it carries over for various other features I hoped to
include. This therefore prevented other features from being implemented – the
notifications for example.
While I hope to broach the topic at a later cycle, it will unfortunately have to be placed on
hold as I attempt to include other, more basic, features within this application.
The development has proven to exceed my expectations of the complexity. This is, in fact,
exhibited by my testing. I feel that – since I perform extensive research while planning a
The first step of programmatic setup is importing SideMenu. It can then be accessed within
the view.
typealias m = SideMenuManager
m.default.menuLeftNavigationController =
storyboard!.instantiateViewController(withIdentifier: "LeftMenuNavigationController") as?
UISideMenuNavigationController
m.default.menuAddPanGestureToPresent(toView: self.navigationController!.navigationBar)
m.default.menuAddScreenEdgePanGesturesToPresent(toView:forMenu:)
m.default.menuPushStyle = .popWhenPossible
m.default.menuPresentMode = .viewSlideInOut
I have specified that I wish the menu to only appear on the left side, with:
m.default.menuLeftNavigationController.
I have also used .popWhenPossible as this will allow for the most logical navigation control,
preventing the user from getting lost down many views.
Finally, I have included and aesthetic feature .viewSlideInOut as I believe it is the sleekest
look.
Testing
To test the menu, the views will be navigated.
The following routes will be taken:
Deep navigation and reversal – agenda -> schedule -> event -> occurrences, and back
again. The expected result is to be able to navigate down, and navigate back up
through the same route.
Cross navigation, navigation through multiple views and switching to another base
view, and navigating back – agenda -> schedule -> alarms -> schedule, and then using
the return array. This should lead back to the agenda directly. The route travelled
there should not be travelled again in reverse.
The menu appears properly, but opens coloured completely black. I have not applied any
colouring to it, so this is surprising. However, going through the documentation for
solutions, it appears this is known bug with a simple fix provided. By including
“default.menuFadeStatusBar = false” to the menu setup (setupSideMenu()).
I have made the change, and it appears to have worked. The menu now appears as
expected.
Cycle 5: Agenda II
Outline
So far, the Agenda has some basic framework, in terms of objectives to achieve and a little
programming. The queue has been programmed. I expect three of those queues will be
initialised to work as a priority queue.
With events having been completed, the Agenda’s features can be implemented.
Upcoming events
o The agenda is to display events upcoming that day. These events are to be
displayed in order that they are to appear, and after the event has begun
they are not to appear on the agenda any longer.
Queueing
o Expanding upon displaying events in order, the queue ensures that the order
is maintained. I already have a queue in place, the only question is
implementation. However, some level of complexity is introduced as I intend
to include a system of priority queues.
Event timing
o The user can see, at a glance, the time at which an event is to occur. The
timer will slowly tick down until it is time for the event to occur.
This cycle tackles the points remaining after the 1st cycle, that could not be included due to
the missing schedule framework.
Development
To begin with, I have made a modification to the cell priority switcher. As the priorities are
defined by the saved pickerview indexes, the switch will be modified to reflect that. Now,
the cases are integers tested against event.priority. Similarly, the cell name is generated by
event.title.
This is all possible as the event is passed by the agenda to the cell.
Queue Implementation
I wish to create a simple priority queue. This will be done by using three instances of the
linked list queue: high, medium, and low priority.
Events are populated to the correct queue based on their priority. I may also include a
mechanism to bump up the priority of events as they approach their occurrence time.
To enqueue, the events for that given day must be obtained. This can be done using a
modification of the toDictionary function used in the schedule. However, the events that
occur on that given day must be extracted.
To begin, the day must be determined. Using a modification of StackOverflow user Ram’s
code (https://stackoverflow.com/questions/46402684/how-to-get-start-and-end-of-the-
week-in-swift), this can be easily achieved. The function returns the current weekday as an
integer (with respect to the seven day week). It is set as Sunday start, whereas I prefer a
Monday start. For that reason, I have included a little clause to return the value less 1. In the
case that it is negative (as Sunday returns 0), return 6.
var weekday = Calendar.current.component(.weekday, from: Date())-2
if weekday == -1 { weekday = 6 }
All saved items are converted into the dictionary format. A second dictionary may then be
created, where the key is the time of occurrence, and the value the event. It essentially
toOrderDict(allDict, weekday){
dictionary = []
func setupView(){
allEvents = uiRealm.objects(RepeatingEvent.self).toArray() as!
[RepeatingEvent]
var weekday = Calendar.current.component(.weekday, from: Date())-2 //get
today's day as a number (week beginning Sunday [-1]), set Monday as 0 index [-1]
if weekday == -1 { weekday = 6 } //due to error on Sundays
if orderDict != orderDictCheck{
return dictionary
}
var count = startTime //the first hour that will appear in the
occurrences
if item != nil{
let event = uiRealm.object(ofType: RepeatingEvent.self,
forPrimaryKey: item)!
When running the application (with events populated for today), I noticed that the events
kept repopulating to the table.
It appears that I forgot to include a statement to clear data from the queue.
For the time being, I will use a quick fix to empty the queues. This is done by setting the
head to nil. Node connected are subsequently lost in memory, which has the potential to
cause serious memory leaks. However, I only intend to use this temporarily for testing the
main Agenda features. It has no effect in the Agenda works, and I completely intend to
implement a memory-safe manner of removing all nodes from the queue.
Cycle 6: Settings
Outline
The application is designed to be usable in any circumstances, including where the user may
have a strange schedule. In that case, they will need to be able to edit the times that the
schedule may show.
A default value is provided for the user, so they will not have to enter settings to make any
configurations before being able to use the application.
Realm supports deleting all data stored as an inbuilt function, so I may also provide this
option to the user.
Finally, after all settings changes have been made, the user will save their settings. If they do
not, then none of the changes will save (except delete all, which works independently).
This feature was not initially planned, however I believe it is an invaluable addition to the
application, and provides much more functionality that the user will need. Unfortunately, no
success criteria exists for this feature, so there is no solid framework to test against. Instead,
I will create criteria to test against here.
The features required for this feature are:
Hour selection – The user will need some way to select the hours between which
they wish their timetable to display.
Display format – the user may have a preference over whether they see the times
displayed in 24-hour format or 12-hour format.
Delete All – the user will be given the option to remove all their data from the
application to make a fresh start.
Hour Selection
A possible way of doing this is with two text fields, one to take the starting hours, and the
other to take the finishing hour. This comes with the drawback that the inputs need to be
validated. The format must be defined to the user, as it is impossible to check against every
possible input by any possible user.
A possible way around this is to use a picker view, where the input processing is handled
internally. However, there needs to be a check to ensure that the user does not select a
period that cannot be displayed, like where the starting hour occurs after the finishing hour.
In either case, I believe that the approaches mentioned above are not very intuitive for the
user experience. There is a third possibility, where a slider is used. While Xcode provides a
slider feature, it only supports a single slider. This is important, as a single slider does not
solve my issue. Instead, I will need a sort of dual slider, where the single slider contains two
controllers.
This immediately solves the issues faced. The user selects their range as being between the
two sliders, and can immediately see the distance between the two sliders, to get a sense of
how large a time they are selecting. The two sliders also will not be able to overlap, or if
they do, the times they represent (start/end time) will switch.
Delete All
Within Realm, the option to clear all data exists. When the user taps the delete all button in
the settings, this function will be called.
Before that happens however, the user must be informed that the action is irreversible. All
of their data will be lost. The user will be given to cancel the action.
Upon performing the delete, since all data has been deleted, some basic structural data will
have to be reformed. The Schedule view, for example, cannot be opened as there will be no
data about what hours should be displayed.
The Realm delete function does not discriminate, and therefore the settings function will be
deleted. However, after the delete has been called, it will simply be recreated, avoiding any
errors as soon as the user exits the view.
Development
Setup
I have included default values, which means the application has values to work with,
without the user having to do anything.
The model includes a check for whether the selected format is 12 or 24-hour. It also includes
the range, defined by the start and end times.
At view load, Settings is pulled from the Realm and is used to populate the view with the
data it has stored. That way, the user will always be working with the data they last
selected.
This can be done using a simple primary key call: settings = uiRealm.object(ofType:
SettingsStore.self, forPrimaryKey: "1")!
A change is required for the model to accommodate this. Two lines have been added:
@objc dynamic var id = "1"
Hour Selection
First of all, a double-ended slider must be found.
To test this, the view will be loaded and various values selected. The values should
correspond with the rough position of the knob.
The label text matches up with the printed values (as expected), which themselves
correspond with the rough position of the knobs.
The values currently output in their raw state. This can be improved by formatting the
hours, as will be detailed in the next section.
Hour Format
As the values are stored and subsequently displayed as doubles (since this is how the slider
handles the values), the values must be formatted so the user can immediately understand
how the hours work.
As some users have a preference of which time format to use, this must also be taken into
consideration. Below, a flowchart has been created which details the logic of outputting the
correct times for the user. It is called every time the user changes a value on the slider.
There may be more efficient methods of output of formatted time, however since it is such
a simple algorithm with a fixed problem size, the efficiency issues are negligible.
This function will take any given integer (which will be between 0 and 24) and will return the
value as a string to be depicted in the display. It will be the user’s decision as to whether the
value is displayed in either time format.
I will create a function which takes any time and returns the appropriate value. The format
check will be performed on the switch. The reason for this, rather than basing it off settings
stored in the Realm, is that the user may switch between the two until they decide on the
one they prefer. Only after they are happy with everything will they save the changes made,
which will commit the changes to the Realm.
If formatSwitch.isOn evaluates to true, then the user has selected 24 hour, otherwise it is in
the default position, so 12 hour times will be displayed.
The conversion will then happen, using the process above. Any numerical methods (like
subtracting 12) will be carried out before the integer is converted into a string.
In Swift, it will look like this.
if formatSwitch.isOn == true{
//generate 24 hour times
if time < 12{
string = "0\(time):00"
}
else{
string = "\(time):00"
}
}
else{
//generate 12 hour times
if time < 12{
if time == 0{
string = "12:00am"
}
else{
string = "\(time):00am"
}
}
else{
if time != 12{
string = "\(time-12):00pm"
}
else{ string = "12:00pm"}
}
}
return string
}
There have been a number of slight changes due to how the implementation must work.
First of all, to add things like “00”, the integer is converted into string and the modifications
made. However, since the time could be any value, the string had to be escaped, which has
been done using ‘\(x)’ where x is the variable.
Furthermore, since the function takes an integer, and the slider returns a double, there has
to be conversion between the two before passing it into the function.
Finally, a function named “setupLabel” has been created to push the text to the label. It uses
similar string escape methods, where the variables being pushed in are the results of the
function to convert the time into its string format.
The function is required as updates may happen when either the switch is tapped, or the
slider changed. The label must be setup again in either case, hence the function.
The output is this for various values:
This function has also been used in the Schedule view (and by extension, the Occurrences
view also), where some modifications have taken place. Since the times are being converted
from a range, the times will be populated to an array. As the function will be embedded
(due to the change in its use), a small change will be made where the function no longer
takes a parameter or returns a value, and instead is an in place array generator, operating
on an array that already exists.
func setupTimes(){
hours = []
if settings.twentyFour == true{
//generate 24 hour times
for x in settings.lowerBound...settings.upperBound{
var string = ""
if x < 12{
string = "0\(x):00"
}
else{
string = "\(x):00"
}
hours.append(string)
}
}
else{
//generate 12 hour times
for x in settings.lowerBound...settings.upperBound{
var string = ""
if x < 12{
if x == 0{
string = "12:00am"
}
else{
Testing
This feature is relatively simple, and I only need to check that the output is correct for any
given value, of which there are few enough that I can observe each.
In the testing table below, I will select various values and check the output.
Save
Once the user has made all the changes they wish, the settings can then be updated to the
Realm. The changes are not immediately updated because that would lead to too many
writes to the database, which is unnecessary. Therefore, once the user is happy, they may
press the save button in the top right corner (created by defining a right bar button item of
type save; it calls the save function whenever tapped [navigationItem.rightBarButtonItem
= UIBarButtonItem(barButtonSystemItem: .save, target: self, action:
#selector(saveTapped))]).
The button takes all the current values of the slider and switch, converts those values to
those that are saved in the database, and then writes the changes.
The variables taken into account:
rangeSlider.lowerValue (double)
rangeSlider.upperValue (double)
formatSwitch.isOn (bool)
Realm update {
settings.lowerBound = Int(rangeSlider.lowerValue)
repeated for upperValue
settings.twentyFour = formatSwitch.isOn
The specifics of the update require the above (encased within the braces) to be performed
within a write transaction.
The transaction begins with “try! uiRealm.write {” and ends with
“uiRealm.add(settings, update: true)” (since the settings are being updated, rather than
a new object being added).
To test, I placed a print statement of the object in the Agenda view, and it appears to be
called and printed out correctly, including the most recent changes made. I can conclude
that saving is carried out correctly.
Delete All
Simply connecting a button to the view controller and then calling the delete function when
it is tapped is incredibly risky. The entire Realm is deleted, which has a high chance of
causing issues, especially since Settings uses data from the Realm and updates to it.
Unexpected issues may occur. Furthermore, the Agenda view makes an immediate Realm
call when loaded, as do most other views. Some safety mechanism must be in place, or an
alternative method found.
For that reason, I looked into the Realm documentation regarding deleted. deleteAll()
removes the entire Realm. I wish to keep this intact, so this therefore is not a viable
approach. I wish to keep the store intact, and only delete the data within.
I did experiment with calling deleteAll() on the button press. I also included a catch to
restore the Settings by using the values present within the view to recreate the file. Despite
this, the app continually crashed whenever the view was left (i.e. another view made a call
to the Realm, which did not exist). I rolled back the changes and noted the experiment as
having proved it was likely too much difficulty to catch all the issues to make it work.
A potential method of doing so is to reference all the objects of a type, and then perform an
individual delete on all of them. This prevents the issues of no Realm existing, but it requires
every object to be cleared to be accessed and manually deleted. This will apply to every
object type, bar settings.
try! realm.write {
realm.delete(allUploadingObjects)
}
Implementation to Views
There are three views that will need to make use of the values in settings. These are the
Schedule, Event, and Occurrences (and by extension, Occurrences cell).
Implementation consists of replacing hardcoded start and end times with the values in the
Settings (obviously the Settings object must be called first).
As I have already based much of the program around the start and end times of the hours to
display (with intermediate hours being procedurally generated), the step of implementation
only consists of replacing the hard integer with the appropriate settings value.
Provided the settings contains a value, it can be assumed that the code carried out
successfully. I will perform a presence check and print the Settings values. They should
match what was set after the most recent edit.
While confirming this, I was also able to check that the views adjusted in size appropriately
to match the settings change, which they have.
I do not believe it is necessary to include any on these views as the Settings will always be
saved with a value. This means that the views will always receive a value regardless, and
validation is unnecessary.
Generation
The hash table will initially be created containing spaces for all 24 hours of each of the 7
days. This means there are 168 spaces available.
As I wish this to match up with how the Schedule table is structures, the hash table will in
fact be two dimensional, where week lookup is performed first, followed by day lookup.
The hash table itself can easily be based on the dictionary structure available within Swift.
The dictionaries already contain key value pairs, and therefore are an ideal structure to
implement an hash table on.
The super-dictionary will contain a reference for each day of the week (like 0-6/1-7, for
example). Each key will then contain its own dictionary, from with keys 0 to 23. Each item in
the table will be filled, possible with an empty string, so that comparisons can always be
made between the value and items being considered by the application.
Using an empty string is ideal as no event can have an empty String as a name since that has
been validated against.
On Schedule open, with every event loaded into an array, each occurrence can be looked up
(using a modification of the cell lookup function in place). As each occurrence is registered,
the relevant place is found within the (empty) hash table, and the space updated with the ID
of the event that takes place then.
Load
The Schedule will access the hash table to generate the cells needed. Each cell uses its
column and row number to access the table (with the appropriate level of shift to account
for the title rows/columns). The access immediately returns the event, if any, that is stored
there.
As it is the ID, a Realm call can be performed immediately, which will return the event,
where it can then be used like normal by the cell.
This feature only address Success Criteria point 3.3.2 (conflict handling). Otherwise, I will
include my own testing plan unrelated to the Criteria.
Test Type Description Data Expected
No. Outcome
1 Functional An event will be added to the table Events The table takes
to ensure it can take events the event and
stores its
occurrence(s)
2 Functional Test of whether conflicts are Events The Occurrences
handled. A conflict event will be picker will not
added to the Schedule. allow the invalid
event to be added
3 Boundary The table will be stress tested to see Events The table takes
whether it can take an event for the events with
every space available no problem and
displays them as
usual.
Table Generation
Generation works on the function already in place in the view controller. It will be copied to
the hash table class and used within it to generate the data within the table. The table itself
will be hardcoded within the class.
It will look like this:
var table = [
//monday
0: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"",
11:"", 12:"", 13:"", 14:"", 15:"", 16:"", 17:"", 18:"", 19:"", 20:"", 21:"",
22:"", 23:""],
//tuesday
2: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"",
11:"", 12:"", 13:"", 14:"", 15:"", 16:"", 17:"", 18:"", 19:"", 20:"", 21:"",
22:"", 23:""],
3: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"",
11:"", 12:"", 13:"", 14:"", 15:"", 16:"", 17:"", 18:"", 19:"", 20:"", 21:"",
22:"", 23:""],
4: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"",
11:"", 12:"", 13:"", 14:"", 15:"", 16:"", 17:"", 18:"", 19:"", 20:"", 21:"",
22:"", 23:""],
5: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"",
11:"", 12:"", 13:"", 14:"", 15:"", 16:"", 17:"", 18:"", 19:"", 20:"", 21:"",
22:"", 23:""],
//sunday
6: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"",
11:"", 12:"", 13:"", 14:"", 15:"", 16:"", 17:"", 18:"", 19:"", 20:"", 21:"",
22:"", 23:""],
]
The generation then works by going through every event and adding its occurrences.
func populateTable(eventsDict: [String: [[Int]]]) -> [Int: [Int: String]]{
return table
}
Table Loading
To use the table now to load cells, I will make a small function to the cell generation
function.
Returning the ID of an event is incredibly simply. It consists simply of this:
func idAtPosition(column: Int, row: Int) -> String{
return table[column]![row]!
}
Conflict Handling
I added the following code to the Occurrences cell generation:
let ID = hashTable.idAtPosition(column: indexPath.section, row:
indexPath.row+settings.lowerBound)
have the hour selection as usual. My initial attempt is making little progress, so I will roll
back the changes, and redesign the feature from the ground up. The basis of the feature is
as follows:
This prevents in-use cells from being selected. The next part is when an event is being
updated, its use within the table must be reconfigured – removing spaces where it was in
place. I have rewritten the hash generator function to be better encapsulated. Views only
call the method and it carries out everything else it must. However, this has meant that the
function may not be as efficient, as it has to generalise to accommodate all possible usage.
Now, whenever the method is called, the table is reset using a template.
It is then re-updated with every item in the database (with the database conversion being
carried out within the class, rather than passed in as a variable).
This does mean that on edit and delete of event, the table does not include now-unused
spaces. Essentially, deleting of event occurrences works correctly.
The next step is to recreate the collision prevention mechanism, as in its current state,
collisions only lead to a rewrite by the most recently edited event, without any information
to the user.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "timesCell", for: indexPath) as! OccurencesCell
return cell
}
Testing consist of opening the picker and seeing what selections can be made.
Testing of the deletion is carried out by removing occurrences and deleting events.
I have tested the possible use cases – adding a new event with no existing events, modifying
an existing event with no other existing events, adding a new event with existing events,
and modifying an event with existing events.
These will be cases 1 to 4.
In case 1, the entire table was clear, which was as expected. I was able to add an event with
no problems.
In case 2, the event had its selected times highlighted, which were selectable and
changeable. Upon removing some times, I returned to the Schedule to see it had updated
correctly.
(Case 3): I then added another event. This time, the Occurrences showed the name of the
event in palace of the time, and would not allow me to select the time.
In case 4, I then modified an event. I was allowed to edit the times selected, and the times
taken by the other event displayed correctly.
It appears that all is working well. To ensure, I deleted the events, and found the schedule
cleared.
I believe the collision detection feature is complete.
Evaluation
Focus Group: They were quite pleased to hear that I had worked to include this feature to
address the issues present off the end of the Schedule cycle. The application at the time
provided no framework to manage events occurring together.
The focus group carried out extensive usability testing for me, in the spirit of attempting to
break the application. This was, in fact, a good thing, as it appears that six dedicated
individuals were unable to cause any issues. It bodes well for use by a typical user.
That does not mean that the application is without bugs, however the focus group were
unable to dig any up regarding the collision detection, despite trying very hard.
Evaluation
Overview
My overall takeaway from developing this application is that it was far more complex than I
had anticipated. This came to be a fundamental issue throughout the entire development
process. My initial plan was far too ambitious for what is realistically possible within the
time period available to develop this application. I had initially intended to include far more
features within this application, however I came to find that I had to simplify the project
significantly to ensure that it could be completed.
While there was no one feature that took an inordinate amount of time, the Schedule and
Events required a large amount of work to complete to an acceptable standard. Even then, I
had not included any collision detection on the first run through. I found that it was possible
to keep thinking of features to add.
They build up and begin to overcomplicate the relevant view controllers. On that note, I
could have structured my solution to better adhere to my architectural plan (Model-View-
Controller).
Of the Success Criteria points I set out, the ones I attempted I broadly met, with the
exception of the Agenda, as I decided to remove that in the end.
All points required for the Schedule and Events were met, which was the most important
feature of the application, as it forms the very core. I was able to include some auxiliary
features within this (the colour picker, for example). Some features were missing however –
the alarms and notifications in particular.
For their implementation, much more work would have been required with integrating the
application with iOS’ notification framework. Furthermore, they had to trigger at a specific
time, requiring analysis of event occurrence. The biggest issue was that they had to repeat
for every event. iOS restricts excessive notifications, which would have killed that particular
aspect of functionality.
Similarly, the Do-Not-Disturb mode could not even be attempted for much the same reason.
iOS sandboxes applications run on the platform. This means they have no access to anything
else on the device, besides what is explicitly given permission by the software. Accessing
hardware of other application must be done through the correct channels. If those channels
do not exist, then the attempted feature cannot be created. This is the case I had with the
Do-Not-Disturb mode.
I wished to affect a system-wide silent mode, however I was restricted as the operating
system provides no access to the hardware or software required to enact it.
Maintenance
I believe the application requires little real-time maintenance. While of course, feature may
become dated and require updating to keep them relevant, and there are always
improvements that can be made to enhance the application (i.e. it is never finished and
constantly being updated/maintained), I believe this application requires no maintenance to
keep it working on user devices.
The reason for this is that the application makes no use of any external features that may
require maintenance themselves. Had I used Firebase as a database solution, for example, I
may have had to plan for how Firebase use may change, and be aware of the possibility of
the service shutting down. This is due to it being an online service, and potentially under
constant changes itself. With Realm, and many of the other external APIs and Pods used,
these are all offline copies and stored locally, meaning they remain unchanged. As they are
unchanged, no work needs to be done to maintain them.
These features may update in the future, which means I may update them within my
application, which leads to some work to ensure that everything is working as intended
within the application.
One particular long-term potential maintenance issue is the application being supported on
future versions of iOS. Some of the features I have used made some usage of Swift 3. While
this is currently supported, it may not remain so. In that case, the feature may have been
updated, and I will carry out the necessary updating steps. However, if the external feature
is not maintained, then I will either have to update it myself to the latest Swift version, or
find another method of solving the problem it tackled previously.
This updating problem only arises as iOS progresses with Swift, and the language is
improved. This problem is somewhat mitigated by intermediate version of Xcode, as they
support both the latest and the previous generation of Swift. The reason for doing so is to
retain a library of the difference, so that the old version may be updated to the syntax of the
new version. This is handled largely automatically (with changes made only need be
confirmed by the developer), so will likely be a small issue – if at all.
Conclusion
Overall, this project had many vicissitudes. My take away from it, most of all, is that I vastly
underestimate the complexity of certain computational features. This may be the result of
numerous high quality applications being widely available, which have left me without the
true appreciate these complex features demand.
This caused me to be far too ambitious with my initial project plan, as many of the features I
wished to include could very well have formed projects of their own. This, of course, was
detrimental to the progress of the project, as I spent a number of hours on features I could
not use in the end, whether this was dedicated to researching or programming, the fact
remains that time was not recovered, and was not spent on features that more realistically
could be used to improve the application.
I did learn a great deal throughout the course of this project. The benefits of proper
planning and time management for two, and a better understanding of various
programming techniques through my implementation of them within the application.
My final takeaway is a greater appreciation of the computer systems around us, and to not
underestimate the true complexity of the programming underneath – especially when
attempting to replicate it myself.
Source Code
Model
App Delegate
//
// AppDelegate.swift
// timetable
//
// Created by Juheb on 30/10/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
import RealmSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Note Model
//
// Note Model.swift
// timetable
//
// Created by Juheb on 14/03/2019.
// Copyright © 2019 Juheb. All rights reserved.
//
import Foundation
import RealmSwift
Settings Model
//
// Settings Model.swift
// timetable
//
// Created by Juheb on 14/03/2019.
// Copyright © 2019 Juheb. All rights reserved.
import Foundation
import RealmSwift
Event Model
//
// Event Model.swift
// timetable
//
// Created by Juheb on 14/03/2019.
// Copyright © 2019 Juheb. All rights reserved.
//
import Foundation
import RealmSwift
extension Results {
func toArray() -> [Any] { //put items into an array
return self.map{$0}
}
extension RealmSwift.List {
func toArray() -> [Any] {
return self.map{$0}
}
}
View Controllers
Settings
//
// SettingsViewController.swift
// timetable
//
// Created by Juheb on 03/12/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
import SwiftRangeSlider
lowerVal = rangeSlider.lowerValue
upperVal = rangeSlider.upperValue
if settings.twentyFour == true{
formatSwitch.isOn = true
setupLabel()
}
else {
formatSwitch.isOn = false
setupLabel()
}
// Do any additional setup after loading the view.
settings.lowerBound = Int(rangeSlider.lowerValue)
settings.upperBound = Int(rangeSlider.upperValue)
if formatSwitch.isOn == true{
settings.twentyFour = true
} else { settings.twentyFour = false }
//Setup alert
let alert = UIAlertController(title: "Warning", message: "This action is not reversible",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
func setupLabel(){
hoursLabel.text = "\(setupTimes(time: Int(lowerVal))) to \(setupTimes(time:
Int(upperVal)))"
}
if formatSwitch.isOn == true{
//generate 24 hour times
if time < 12{
string = "0\(time):00"
}
else{
string = "\(time):00"
}
}
else{
//generate 12 hour times
if time < 12{
if time == 0{
string = "12:00am"
}
else{
string = "\(time):00am"
}
}
else{
if time != 12{
string = "\(time-12):00pm"
}
else{ string = "12:00pm"}
}
}
return string
}
Agenda
//
// Agenda.swift
// timetable
//
// Created by Juheb on 30/10/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
import SideMenu
// Enable gestures. The left and/or right menus must be set up above for these to work.
// Note that these continue to work on the Navigation Controller independent of the
View Controller it displays!
m.default.menuAddPanGestureToPresent(toView:
self.navigationController!.navigationBar)
Notes
//
// Note.swift
// timetable
//
// Created by Juheb on 12/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
import RealmSwift
//MARK: Setup
override func viewDidLoad() {
super.viewDidLoad()
alertController.addAction(dismissAction)
titleField.text = currentNote.title
bodyField.text = currentNote.body
}
titleField.delegate = self
bodyField.delegate = self
//MARK: Save
@IBAction func saveButtonPressed(_ sender: UIBarButtonItem){
if (titleField.text == "" || bodyField.text == "") { //check if empty
alertController.title = "Warning"
alertController.message = "Please leave no empty fields when saving"
self.present(alertController, animated: true, completion: nil)
//if empty, present warning alert
}
else if (bodyField.text == "Note Description" || titleField.text == "Title"){
alertController.title = "Warning"
alertController.message = "Please add your own text or cancel"
self.present(alertController, animated: true, completion: nil)
//if left to default, present warning alert
}
else{
if addNoteSegue == true{
uiRealm.add(currentNote)
}
}
else{
try! uiRealm.write { //place all updates within a transaction
currentNote.title = titleField.text!
currentNote.body = bodyField.text!
currentNote.age = currentDateTime
self.navigationController!.popViewController(animated: true)
}
//MARK: Cancel
@IBAction func cancelButtonPressed(_ sender: UIBarButtonItem){
self.navigationController!.popViewController(animated: true)
}
return false
}
import UIKit
import RealmSwift
//MARK: Setup
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 80
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
tableView.tableHeaderView = searchController.searchBar
//defines the search bar's actions
self.tableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
searchController.dismiss(animated: false, completion: nil)
}
//MARK: Search
//MARK: Sort
@IBAction func didSelectSort(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0{
// Date
filteredNotes = []
for date in ageArray{
//match item to allnote object name and output item in correct position in filtered
array
let newItem = uiRealm.objects(NoteData.self).filter("age = '\(date)'")
filteredNotes.append(newItem.first!)
}
}
}
else{
// A-Z
for singleNote in filteredNotes{
titleArray.append(singleNote.title)
}
if titleArray.count > 0{
titleArray = sort.quickSort(titleArray)
filteredNotes = []
//MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showNote"{
if let indexPath = self.tableView.indexPathForSelectedRow { //get indexPath.row
here instead of did select row function
tappedId = (allNotes[indexPath.row] as AnyObject).id
}
let destinationVC = segue.destination as! Notes
destinationVC.addNoteSegue = false
destinationVC.noteId = tappedId
}
else if segue.identifier == "addNote"{
Notes Cell
//
// NotesCell.swift
// timetable
//
// Created by Juheb on 12/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
Schedule
//
// ViewController.swift
// SpreadsheetView
//
//
import UIKit
import SpreadsheetView
setupTimes()
spreadsheetView.dataSource = self
spreadsheetView.delegate = self
hashTable.populateTable(timeDifference: settings.lowerBound)
//load data
//reload times
self.spreadsheetView.reloadData()
//reload data when view loads
}
// MARK: DataSource
func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int {
return 1 + days.count
}
if text != "" {
cell.label.text = text
var colour = UIColor()
cell.label.textColor = colour
/// Delegate
if segue.identifier == "editEventSegue"{
let destinationVC = segue.destination as! EventVC
destinationVC.eventName = name //send event name
}
func setupTimes(){
hours = []
if settings.twentyFour == true{
//generate 24 hour times
for x in settings.lowerBound...settings.upperBound{
var string = ""
if x < 12{
string = "0\(x):00"
}
else{
string = "\(x):00"
}
hours.append(string)
}
}
else{
Cells
//
// Cells.swift
// SpreadsheetView
//
// Created by Kishikawa Katsumi on 5/8/17.
// Copyright © 2017 Kishikawa Katsumi. All rights reserved.
//
import UIKit
import SpreadsheetView
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.boldSystemFont(ofSize: 10)
label.textAlignment = .center
contentView.addSubview(label)
}
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textAlignment = .center
contentView.addSubview(label)
}
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.boldSystemFont(ofSize: 12)
label.textAlignment = .center
contentView.addSubview(label)
}
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight:
UIFont.Weight.medium)
label.textAlignment = .right
backgroundView = UIView()
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.boldSystemFont(ofSize: 12)
label.textAlignment = .left
contentView.addSubview(label)
}
Event
//
// Event.swift
//
//
// Created by Juheb on 11/11/2018.
//
import UIKit
//MARK: Variables
var repeatingEvent = RepeatingEvent()
var hashTable = HashTable()
var settings = SettingsStore()
var check = 1 //edit disabled
let priorities = ["Normal", "Important", "Urgent"]
//Sent variables
var eventName: String!
var repeating: Bool!
//Recieved variable
var occurences: [[Int]]?
var colour: String?
repeatSwitch.isHidden = true
switchLabel.isHidden = true
repeating = true
nameLabel.delegate = self
descriptionLabel.delegate = self
priorityPicker.delegate = self
priorityPicker.dataSource = self
occurences = [[], [], [], [], [], [], []] //setup occurences
if eventName == "" { //setup in all cases when a new event is being added
}
}
if (occurences == [[], [], [], [], [], [], []]) || (nameLabel.text == "" || nameLabel.text ==
"Event Name") || (colour == nil){
valid = false
} else {valid = true}
self.hashTable.populateTable(timeDifference: self.settings.lowerBound)
self.present(successAlert, animated: true)
}
else{
defaultAlert.title = "Info"
event.id = name
for day in week{ //for every day in the week, append the day to the week
event.week.append(timesToDay(times: day))
}
return event
}
for day in week{ //for every day in the week, append the day to the week
return weekOccurences
}
if set == false{
priorityPicker.alpha = 0.5
priorityLabel.alpha = 0.5
colourPickerButton.alpha = 0.5
occurenceButton.alpha = 0.5
//submitButton.alpha = 0.5
}
else{
priorityPicker.alpha = 1
//MARK: Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//TODO: Fix segue
if segue.identifier == "occurenceSegue"{
//send over occurence data
let destinationVC = segue.destination as! OccurencesTVC
destinationVC.occurences = occurences
destinationVC.eventName = eventName
}
else if segue.identifier == "colourPickerSegue"{
let destinationVC = segue.destination as! ColourPickerViewController
destinationVC.colour = colour
}
OccurrencesTVC
//
// OccurencesTVC.swift
// timetable
//
// Created by Juheb on 23/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
self.tableView.allowsMultipleSelection = true
self.tableView.allowsMultipleSelectionDuringEditing = true
setupTimes()
return cell
}
if selected == nil{
let alert = UIAlertController(title: "Warning", message: "No times were added \n Are
you sure you want to return?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { action in
self.navigationController?.popViewController(animated: true) //return and send
empty data to EventVC
}))
self.present(alert, animated: true)
}
else{
}))
self.present(alert, animated: true)
}
func setupTimes(){
hours = []
if settings.twentyFour == true{
//generate 24 hour times
for x in settings.lowerBound...settings.upperBound{
var string = ""
if x < 12{
string = "0\(x):00"
}
else{
string = "\(x):00"
}
hours.append(string)
}
}
else{
//generate 12 hour times
for x in settings.lowerBound...settings.upperBound{
var string = ""
if x < 12{
if x == 0{
string = "12:00am"
}
else{
string = "\(x):00am"
}
}
else{
if x != 12{
string = "\(x-12):00pm"
}
else{ string = "12:00pm"}
}
//MARK: Navigation
func submitAndReturn() { //sends data and returns to previous view
let stack = self.navigationController?.viewControllers
let previousView = stack![stack!.count - 2] as! EventVC
previousView.occurences = occurences
self.navigationController?.popViewController(animated: true)
}
Occurrences Cell
//
// OccurrencesCell.swift
// timetable
//
// Created by Juheb on 23/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import UIKit
import UIKit
if colour == nil{
pickerController?.color = UIColor.red
label.text = colour
label.backgroundColor = pickerController?.color!.withAlphaComponent(0.2)
label.textColor = pickerController?.color
}
else {
pickerController?.color = UIColor(hex: colour!)
label.text = colour
label.backgroundColor = pickerController?.color!.withAlphaComponent(0.2)
label.textColor = pickerController?.color
}
/* you shoudln't interact directly with the individual components unless you want to do
customization of the colorPicker itself. You can provide a closure to the pickerController,
which is going to be invoked when the user is changing a color. Notice that you will receive
intermediate color changes. You can use these by coloring the object the User is actually
trying to color, so she/he gets a direct visual feedback on how a color changes the
appearance of an object of interet. The ColorWell aids in this process by showing old and
new color side-by-side.
*/
pickerController?.onColorChange = {(color, finished) in
}
}
Classes
Linked List Queue
//
// Linked List Queue.swift
// timetable
//
// Created by Juheb on 05/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
//MARK: Enqueue
func enqueue(key: T) {
let nextItem = Node(data: key) //item to be added as node
if let lastNode = last {
lastNode.next = nextItem //last node moved to added item
} else {
head = nextItem //unless item is added to empty queue
}
}
//MARK: Dequeue
func dequeue() -> T? {
if self.head?.data == nil { return nil } //cannot dequeue if queue empty
head = nil
}
//MARK: Length
func length() -> Int{
var count = 0
array.append(node.data)
while case let next? = node.next {
node = next
array.append(node.data)
}
return array
}
}
Sorts
//
// Sorts.swift
// timetable
//
// Created by Juheb on 19/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import Foundation
class Sorts {
if arr.count > 1{
let pivot = arr[arr.count/2]
}
else{
return arr
}
}
if array.count == 1{
return array
}
else{
let split = array.count/2
let leftSide = mergeSort(Array(array[0..<split]))
let rightSide = mergeSort(Array(array[split..<array.count]))
return array
}
Hash Table
//
// Hash Table.swift
// timetable
//
// Created by Juheb on 12/03/2019.
// Copyright © 2019 Juheb. All rights reserved.
//
import Foundation
var globalTable = [
//monday
0: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
//tuesday
1: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
2: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
3: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
4: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
5: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
//sunday
6: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
]
class HashTable{
var resetTable = [
//monday
0: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
//tuesday
1: [0:"", 1:"", 2:"", 3:"", 4:"", 5:"", 6:"", 7:"", 8:"", 9:"", 10:"", 11:"", 12:"", 13:"", 14:"", 15:"",
16:"", 17:"", 18:"", 19:"", 20:"", 21:"", 22:"", 23:""],
globalTable = resetTable
Extensions
//
// UIColor+Hex.swift
// timetable
//
// Created by Juheb on 15/11/2018.
// Copyright © 2018 Juheb. All rights reserved.
//
import Foundation
import UIKit
extension UIColor {
var toHexString: String {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
self.getRed(&r, green: &g, blue: &b, alpha: &a) //built-in call that returns values between
0 and 1 for each channel
return String(
format: "%02X%02X%02X", //00 padding on 3 hex digits
Int(r * 0xff),
Int(g * 0xff),
Int(b * 0xff)
)
}
let r = (rgbValue & 0xff0000) >> 16 //bitmasked and bitshifted to produce values
let g = (rgbValue & 0xff00) >> 8
let b = rgbValue & 0xff
self.init(
red: CGFloat(r) / 0xff, //divided by hex 255 to turn back into val between 0-1,
compatible with uicolor
green: CGFloat(g) / 0xff,
blue: CGFloat(b) / 0xff, alpha: 1
)
}
}
extension Dictionary where Value: Equatable { //gets the key for input value of dictionary.
may be useless now
func allKeys(forValue val: Value) -> [Key] {
return self.filter { $1 == val }.map { $0.0 }
}
}
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor(red: 80.0/255.0, green: 80.0/255.0, blue: 80.0/255.0,
alpha: 0.25).cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 0.0
}
}
extension UITableView {
self.backgroundView = messageLabel
self.separatorStyle = .none
}
func restore() {
self.backgroundView = nil
self.separatorStyle = .singleLine
}
}
return eventTimes
}
}
External Files
Picker Image
// Created by Matthias Schlemm on 05/03/15.
// Copyright (c) 2015 Sixpolys. All rights reserved.
// Licensed under the MIT License.
// URL: https://github.com/MrMatthias/SwiftColorPicker
import UIKit
import ImageIO
let bitsPerComponent:UInt = 8
let bitsPerPixel:UInt = 32
// MARK: Lifecycle
init(width:Int, height:Int) {
self.width = width
self.height = height
var currentBrightnessIndex = 0
while currentBrightnessIndex < brightnessSteps {
var currentSaturationIndex = 0
while currentSaturationIndex < saturationSteps {
currentSaturationIndex += 1
}
currentBrightnessIndex += 1
}
// }
}
Colour Picker
// Created by Matthias Schlemm on 05/03/15.
// Copyright (c) 2015 Sixpolys. All rights reserved.
// Licensed under the MIT License.
// URL: https://github.com/MrMatthias/SwiftColorPicker
import ImageIO
import UIKit
}
fileprivate var currentPoint:CGPoint = CGPoint.zero
func commonInit() {
isUserInteractionEnabled = true
clipsToBounds = false
self.addObserver(self, forKeyPath: "bounds",
options: [NSKeyValueObservingOptions.new,
NSKeyValueObservingOptions.initial],
context: nil)
}
if pickerImage1 == nil {
self.pickerImage1 = PickerImage(width: Int(bounds.width), height: Int(bounds.height))
self.pickerImage2 = PickerImage(width: Int(bounds.width), height: Int(bounds.height))
}
#if !TARGET_INTERFACE_BUILDER
opQueue.addOperation { () -> Void in
// Write colors to data array
if self.data1Shown { self.pickerImage2!.writeColorData(hue: self.h, alpha:self.a) }
else { self.pickerImage1!.writeColorData(hue: self.h, alpha:self.a) }
// flip images
self.image = self.data1Shown ? self.pickerImage2!.getImage()! :
self.pickerImage1!.getImage()!
self.data1Shown = !self.data1Shown
}
#else
self.pickerImage1!.writeColorData(hue: self.h, alpha:self.a)
self.image = self.pickerImage1!.getImage()!
#endif
}
Color Well
// Created by Matthias Schlemm on 05/03/15.
// Copyright (c) 2015 Sixpolys. All rights reserved.
// Licensed under the MIT License.
// URL: https://github.com/MrMatthias/SwiftColorPicker
import UIKit
func commonInit() {
backgroundColor = UIColor.clear
isOpaque = false
}
col.setFill()
ovalPath.fill()
}
borderColor.setStroke()
ovalPath.lineWidth = borderWidth
ovalPath.stroke()
}
import UIKit
// Hue Picker
open var huePicker:HuePicker
// Color Well
open var colorWell:ColorWell {
didSet {
huePicker.setHueFromColor(colorWell.color)
colorPicker.color = colorWell.color
}
}
// Color Picker
open var colorPicker:ColorPicker
Hue Picker
// Created by Matthias Schlemm on 05/03/15.
// Copyright (c) 2015 Sixpolys. All rights reserved.
// Licensed under the MIT License.
// URL: https://github.com/MrMatthias/SwiftColorPicker
import UIKit
func renderBitmap() {
if bounds.isEmpty {
return
}
if data == nil {
data = [UInt8](repeating: UInt8(255), count: Int(width * height) * 4)
}
var p = 0.0
var q = 0.0
var t = 0.0
var i = 0
//_ = 255
var double_v:Double = 0
var double_s:Double = 0
let widthRatio:Double = 360 / Double(bounds.width)
var d = data!
for hi in 0..<Int(bounds.width) {
let double_h:Double = widthRatio * Double(hi) / 60
let sector:Int = Int(floor(double_h))
let f:Double = double_h - Double(sector)
let f1:Double = 1.0 - f
double_v = Double(1)
double_s = Double(1)
p = double_v * (1.0 - double_s) * 255
q = double_v * (1.0 - double_s * f) * 255
t = double_v * ( 1.0 - double_s * f1) * 255
let v255 = double_v * 255
i = hi * 4
switch(sector) {
case 0:
d[i+1] = UInt8(v255)
d[i+2] = UInt8(t)
d[i+3] = UInt8(p)
case 1:
d[i+1] = UInt8(q)
d[i+2] = UInt8(v255)
d[i+3] = UInt8(p)
case 2:
d[i+1] = UInt8(p)
d[i+2] = UInt8(v255)
d[i+3] = UInt8(t)
case 3:
d[i+1] = UInt8(p)
d[i+2] = UInt8(q)
d[i+3] = UInt8(v255)
case 4:
d[i+1] = UInt8(t)
import Foundation
import SideMenu
/*
// Set up a cool background image for demo purposes
let imageView = UIImageView(image: UIImage(named: "saturn"))
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = UIColor.black.withAlphaComponent(0.2)
tableView.backgroundView = imageView
*/
//cell.blurEffectStyle = SideMenuManager.default.menuBlurEffectStyle
return cell
}
Pod File
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'timetable' do
# Comment the next line if you're not using Swift and don't want to use dynamic
frameworks
use_frameworks!
pod 'SpreadsheetView'
pod 'RealmSwift'
pod 'SideMenu'
pod 'SwiftRangeSlider'
target 'timetableTests' do
target 'timetableUITests' do
inherit! :search_paths
# Pods for testing
end
end