This blog is about the implementation of an application for Apple's iOS platform, which can handle repeating tasks better than the currently available alternatives on the market. The members of the team are Robin Stegk and Agnes Klein.

Help to manage repeating but flexible tasks

Hello everybody. Welcome to our blog.

This blog is about the implementation of an application for Apple's iOS platform, which can handle repeating tasks better than the currently available alternatives on the market. The members of the team are Robin Stegk and Agnes Klein.

Project description:

Repeat is supposed to be an app, which can help to manage repeating but flexible tasks. Most calendars and to-do apps let you set a timespan after which a task or date should recur. The problem with them is, that they are fix and not flexible. If you have for example the task to buy medicine for your cat which it should be given to every 10 - 14 weeks and you set your task to repeat every 12 weeks. If you now manage to stretch the time every time to 14 weeks, the date is set nevertheless to repeat every 12 weeks. Which means that the next reminder comes after just 10 weeks and the one after that only after 8 weeks, which would be too soon. 

Repeat jumps in here and fixes this problem by setting the next date only after the task has been marked as completed.Furthermore Repeat will check the location. This makes it possible to only notify the user about a task when it makes sense because he is at the location where he can actually complete the task. Also it enables Repeat to remind the user if he comes by a location where he could complete a task even if it is not exactly at 12 weeks - to refer back at the medicine example - but at 10 weeks. So it enables the user a way more flexibility than the current available solutions known to us.

Project time plan:

  • 3 weeks Architecture and design of the app. Also learning how to program iOS and swift.
  • 2 weeks Programing of the business logic and database.
  • 2 weeks Programming of the user interface.
  • 1 weeks Test and some buffer 
Continue reading
Rate this blog entry:
0
1011 Hits 0 Comments

Project Start

We set up a new iOS App project with the XCode IDE. Due to the official iOS developer site and some online Tutorials, we get familiar with the concepts in the iOS app development and gain a deeper understanding of the Swift programming language.

The Syntax is concise and similar to the programming language C++. Nevertheless, Swift has some unique characteristics and features too. Just to name a few, Swift has lazy properties for delay object creations and so-called Optionals are Swift's way of unifying the representation of Nothingness. If there is a need to return multiple values from a method you can use named tuples instead of objects.

Because of Repeat needs some map-functionalities, we decided to integrate the iOS MapKit into the project to obtain and store location-based tasks from the user. This is necessary to make it possible for the user to receive location based notifications.

Furthermore we did put some thoughts into the architecture of Repeat. Because of some missing knowledge about the frameworks in iOS, we only made a rough plan so far. Due to the design of iOS programming, it’s obvious to take the MVC pattern. This is because there are already Views, ViewController and a Model generated from the CoreData design. 

So after thinking about what parts we need in the app (database, maps, notifications, …), we made the following architecture rough sketch:

View:  

  • Overview (contains the tasks which are active)
  • Add View (create a new task. With nice date picker and text boxes to make it easy to create a new task)
  • Edit View (opens up to edit an existing view. Should look very similar to the “add view”, maybe even use the same)

Controller:  

  • Date-handler (helper for dates in iOS. Just for more convenience when working with dates. Like calculating the difference between two dates and conversion of units)
  • Location-handler (calculations for locations like the distance of two points, or the radius of the circle around the position where the user should be notified)
  • Database-handler (basic methods to create and obtain database objects, save and edit them.)
  • Notification-handler (notify the user when a countdown has reached the end and a task is due)
  • Countdown-handler (because of the special use-case where the countdown should also be resetable by the user, the countdown calculation is a little more complicated. So this class should handle that)

Model:  

  • Task
    • Name
    • Start Date
    • End Date
    • Timespan (countdown time)
    • Countdown timestamp 
  • Location
    • Longitude
    • Latitude
    • Radius

After starting to work with the different frameworks we are sure, with more insight there will come changes. But this is the skeleton on which we should be able to build the app with a clear structure in mind.

Continue reading
Rate this blog entry:
0
935 Hits 0 Comments

Structure and Design of the User Interface

After the architecture of the app, we thought about the structure and the design of the user interface. We broke down the functionality to four key parts of the app:

  1. Home screen - should display all tasks.
  2. New task screen - create a new tasks
  3. Edit task screen - change the values of an existing task
  4. Map screen - Let the user pick a location for a task

Home Screen

The main problem with the home screen was the design of the tasks. We thought about many different solutions to handle the tasks in a way that the user can recognise immediately which ones are due soon and which ones are due later in time. There where ideas like representing tasks as balloons which grow bigger with shorter due times or circles which are “filled with liquid” and when you check them, they leek the liquid. The problem with ideas like that were, that the space on a smartphone is to limited. So to create something like the balloons would mean that the user would have to scroll a lot, which is a bad user experience. So in the end we decided to go with a more conservative design persisting of bars which fill from left to right. 

Because the home screen is also the center of the app the user should be able to do as much as possible directly from there. Which means the user should be able to create and delete tasks, check them and get an overview of all the existing tasks. All of this functionality is build right in our design. Illustrated in the image:

 

     Home 

Add Task Screen

The next screen was the add task screen. The functionality is clear. This screen must give the user the ability to create a tasks and tweak all the parameters he wants to. The parameters can be derived from the architecture where the tasks was defined. So there needs to be the possibility to change the name, the start and end date, the timespan as well as the location. 

Additionally it’s necessary to let the user choose how the task should behave when it’s marked as done. There are two possibilities, it can be reset and the next countdown is set to the time of completion plus the countdown time the user chose for his task, or it can be reset and the next countdown time will be the next date which is calculated from the start date plus X times the timespan until the newly calculated date is in the future. 

   Repeat

For now there is a switcher to choose between the two. For the first possibility there is the “Start new after tap” switch, and for the second possibility there ist the “Start new in time” switch. The problem is in our opinion that this isn’t clear to the user without explanation, so we are still searching for something better here. Furthermore, the user can set the location reminder to obtain a push notification if he is in a certain radius from the task-location. This radius can be modified by the user, too.

Edit Task Screen

The edit screen in our case does nothing special, which the add screen doesn’t do as well. So far simplicity reasons we decided to take the same screen to edit a task. But when a task was chosen to be changed, the values are preset to does saved in the task.

Map Screen

When the user sets the location reminder, another view - the Map Screen - will appear. For realizing this map view, we decided to use the MapKit Framework of iOS for our project.

After detecting the current user location, the map view will be automatically zoomed into it. In the Map Screen the user has two options to set a location. One is to place a pin with a longpress gesture on the map view. If  the user perhaps just knows the address of a location, he can enter this to the given edittext-field on the bottom of the screen. In this case the pin will automatically set on the right location. For every task the user can determine just one corresponding location. In the following figure the preliminary design of the map Screen is shown. With the check-button the location will be saved in the Core Data Database and appears as a thumbnail under the “Set Location reminder”-switch of the prior Add Task Screen.

                                                         Map

 

 

Continue reading
Rate this blog entry:
0
943 Hits 0 Comments

Business Logic

Core Data

In order to be able to save and access the tasks we had to use a database. The recommended way on iOS is to use core data. Core data is an abstraction layer from Apple which can work with objects (it's also called object graph). It abstracts the database so the data in the database can be used as objects from the business logic layer. Typically, it uses SQLite as a database implementation but it can be changed to different kinds of underlying databases.

 

As a starting point we used this (http://www.raywenderlich.com/115695/getting-started-with-core-data-tutorial) blog post. After programming the example and then implementing it in our App however, we decided that it’s a little bit complicated to write all the code all the time, necessary to handle objects. So we wrote a helper class with some easy to use methods. For example to get all the tasks from the database it was necessary to write all this code: 

 

        let predicate = NSPredicate(format: "SELF = %@", repeatable)

        

        let fetchRequest = NSFetchRequest(entityName: REPEATABLE_ENTITY_NAME)

        fetchRequest.predicate = predicate

        do {

            let fetchedEntities = try context.executeFetchRequest(fetchRequest) as! [Repeatable]

            if let repeatable = fetchedEntities.first {

                return repeatable

            }

        } catch {

            // Do something in response to error condition

        }

   

Now after having implemented the helper class, it’s just one method call:

 

         Database.getAllRepeatables(context)

 

Of course we also implemented methods to get the database context, get a new task, get a single task by id or delete a task. The same goes for the locations. 

 

Date calculation

Dates in swift are unfortunately still the same as they were in objective-C. Unfortunately because it’s still kind of complicated. They should have done something like C# were date handling is a blast. 

So because we need countdowns and time differences between dates in different units, we decided to write a helper class like we did for the database. The basics we got from blog posts on the website http://www.globalnerdy.com/2015/01/26/how-to-work-with-dates-and-times-in-swift-part-one/ and http://www.globalnerdy.com/2015/01/29/how-to-work-with-dates-and-times-in-swift-part-two-calculations-with-dates/. 

So for example the method for the time difference between now and a date looks like this: 

 

    // Returns the difference with days, hours, minutes and seconds

    static func diffToNow(date: NSDate) ->  NSDateComponents {

        let userCalendar = NSCalendar.currentCalendar()

        let dayCalendarUnit: NSCalendarUnit = [.Day, .Hour, .Minute, .Second]

        let dateToNowDifference = userCalendar.components(

            dayCalendarUnit,

            fromDate: NSDate(),

            toDate: date,

            options: [])

        

        return dateToNowDifference

    }

 

The problem is that the units which are returned in the object have to be defined up front. So in this example only the days, hours, minutes, and seconds are returned. If one would like to access the years it would fail. In Order to get the years as well, the NSCalenderUnit array would need to look like this: 

 

      let dayCalendarUnit: NSCalendarUnit = [.Year, .Day, .Hour, .Minute, .Second]

 

Another problem was, that we cannot save NSDateComponents in core data. It is only possible to save NSDate as datatype. Because we need to save the timespan how long a countdown should be running, we decided to just convert it to seconds and save it as Integer. But when calculating the dates and new countdowns we convert it back to NSCalendarUnits, because it’s easier to handle. 

 

iOS MapKit

MapKit is an iOS-API, that is used to display maps, jump to coordinates, plot locations and draw routes and shapes on a map. In our case we need to integrate a MKMapView as a base view for realizing the Map Screen. Furthermore we have to tell the MapKit the users location to obtain a map which shows his location. But for the use of such sensitive data, of course, user-permissions are required. 

 

First we had to create a MapViewController and import the MapKit framework at the top of the file as shown below. After the creation of a MapView- and a LocationManager variable (to obtain the user location), the viewDidLoad method had to be changed. This method is called when the view is loaded into memory. Due to this method is executed only once during the life cycle of a view controller object, it is a suitable place for setup methods and view initialization. 

 

Here we adapted the configuration of the CLLLocationManager object instance to use the 

“best accuracy” setting to obtain the user location as accurate as possible. We also declared the view controller instance as the application delegate for the location manager and the map view object. Delegation is a design pattern in Swift that enables a class to delegate some of its responsibilities to an instance. A delegation is mostly used to respond to a particular action, or to retrieve data from an external source without needing to know the underlying type of that source (Explanation of the iOS Developer Library). 

 

       import MapKit

 

       class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

 

           private var mapView = MKMapView()

           private var locationManager: CLLocationManager()

 

                override func viewDidLoad() {

                super.viewDidLoad()

 

               locationManager.delegate = self

               locationManager.desiredAccuracy = kCLLocationAccuracyBest

               locationManager.startUpdatingLocation()

               locationManager.requestAlwaysAuthorization()

        

               mapView.mapType = .Standard

               mapView.frame = view.frame

               mapView.delegate = self

        

               view.addSubview(mapView)

       }

 

Project properties can be saved in a plist-file. For the location permissions we first implemented „NSLocationWhenInUsageDescription“ in the plist and also made a call to locationManager.requestWhenInUseAuthorization() to ask the user for the permission to track location information. But than we realized that this permission just allows us to track the location information while the application is in the foreground. But later we also need the opportunity for sending Push Notifications that notifies the user when he is in a certain area of a given task. For this we needed a stronger permission and implemented „NSLocationAlwaysUsageDescription“ in the plist. With the call of locationManager.requestAlwaysAuthorization(), the user will now be asked for a permission that allows Repeat to access his location even when he is not using this app.

 

The call of the startUpdatingLocation method ensures that the locationManager will be updated every few seconds about the current user location which is seen below.

 

       func locationManager(locationManager: CLLocationManager, didUpdateLocations locations: [CLLocation]){

               if let loc = self.mapView.userLocation.location {

                   let region = MKCoordinateRegionMakeWithDistance(

                       loc.coordinate, 500, 500)

                  self.mapView.setRegion(region, animated: true)

               }

        }

Continue reading
Rate this blog entry:
0
2001 Hits 0 Comments

Map Functionalities and Notifications

Final Map Screen

 

After implementing our first version of the map screen, we decided to add a radius picker on the bottom of the map screen to make the user more comfortable to select a radius for the location based notifications. This picker will only be enabled, if the user chose a location with a longpress gesture or if he inserts an address in the search bar which is attached to the navigation bar. The selection of another radius effects the automatic adaptation of the location radius on the screen. In addition, a simple tap on the pin shows a popup that includes either the street name and number if available, or the exact coordinates (Longitude and Latitude) of the selected location.

 

Bildschirmfoto 2016 01 04 um 21.42.35

 

To detect user gestures we used the UIGestureRecognizer classes of Swift. These classes provide a default implementation to detect common gestures like taps, swipes, and long presses. To realize a longpress recognizer, we specified a callback function „longPressGesture“ to receive notifications when the gesture starts, changes, or ends. This recognizer is associated with the mapView in the viewDidLoad - method:

 

override func viewDidLoad() {

 

        super.viewDidLoad()

        let longPress = UILongPressGestureRecognizer(target: self, action: "longPressGesture:")

        longPress.minimumPressDuration = 1.0

        mapView.addGestureRecognizer(longPress)

 

}

 

The longPressGesture function, as shown below, detects the exact location of the user gesture made within the map view. This point can be converted to coordinates (CLLocationCoordinate2D). 

MapKit also provides methods to get address details - if available - from these coordinates. To convert the users input in the search bar to coordinates and to show the street name of an annotation by tapping, we used MapKit’s CLGeocoder class. Therefore, the reverseGeocodeLocation method was important to convert a location from address and an address from a location (coordinates).

 

func longPressGesture(gestureRecognizer:UIGestureRecognizer) {

 

   let touchPoint = gestureRecognizer.locationInView(mapView)

            let newCoord:CLLocationCoordinate2D = mapView.convertPoint(touchPoint, toCoordinateFromView: self.mapView)

            

            //add pin with radius

            addAnnotationWithRadius(newCoord)

            

            if gestureRecognizer.state == UIGestureRecognizerState.Ended {

                let geoCoder = CLGeocoder()

                lastLocation = CLLocation(latitude: newCoord.latitude as CLLocationDegrees, longitude: newCoord.longitude as CLLocationDegrees)

                geoCoder.reverseGeocodeLocation(lastLocation, completionHandler: { (placemarks, error) -> Void in

                    

                    if placemarks != nil {

                       let placeArray = placemarks! as [CLPlacemark]

             

                        var placeMark: CLPlacemark!

                        placeMark = placeArray[0]

                        

                        // Street name and number

                        if let street = placeMark.addressDictionary!["Street"] as? NSString {

                            print(street)

                            self.annotation.subtitle = street as String

                        }

                        else{

                            // Coordinates

                            let lat:Double = (placeMark.location?.coordinate.latitude)!

                            let lon:Double = (placeMark.location?.coordinate.longitude)!

                            self.annotation.subtitle = "LAT: \(lat) LON: \(lon)"

                        }

                    }

                    

                })

            }

}

 

Saved Locations

 

We appended to the bottom of the Add Task Screen a small map view which will only be visible after the user has chosen a location. This view shows the selected location and radius where the location based notification will be triggered when the user enters this region.

 

Bildschirmfoto 2016 01 04 um 21.42.51

 

Notifications

 

Local notifications are a simple way to display information to the user when the app is in the background. For Repeat we implemented two kinds of local notifications. One notifies the user, if a task is expired and another if he enters a given task area.

 

Location Based Notifications

 

To use location based notifications it is necessary to allow Repeat location access. Therefore, the user permission NSLocationAlwaysUsageDescription is essential. It allows the app to access the users location even when he is not using the app. Every time, when a task with an associated location is saved or updated in the database, the method registerUserMonitoring will be called. The LocationManager provides a function startMonitoringForRegion that notifies him if the user is entering this area (didEnterRegion). This location based notfication will only be send if the next countdown is within 24 hours.

 

func registerUserMonitoring(location: Location){

        

        let latitude = location.latitude!.doubleValue

        let  longitude = location.longitude!.doubleValue

        let radius = location.radius!.doubleValue

        

        let monitorRegion = CLCircularRegion(center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), radius: radius)

        locationManager.startMonitoringForRegion(monitorRegion)

 

}

 

func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {

        

        self.repeatable = self.newRepeatableView.getData()

                

       if self.repeatable.getSecondsToNextCountdown() <= DateHelper.daysToSecond(1) {

            Notifications.setLocationNotification(self.repeatable)

        }

 

}

 

Bildschirmfoto 2016 01 04 um 21.43.16

 

Time Based Notifications

 

A UILocalNotification object specifies a notification that an app can schedule for presentation at a specific date and time (https://developer.apple.com/library/ios/documentation/iPhone/Reference/UILocalNotification_Class/).

Instances of UILocalNotification have a userInfo property ([NSObject : AnyObject]?) that we used to give the notifications unique names with the usage of universally unique identifier (UUID) as shown below. This was important for later modifications of the notification dates if the user resets a task for example. Local notifications are automatically unscheduled after they are fired.

 

    private static func setNotification(fireDate:NSDate, repeatable:Repeatable){

        

        let fireDateOfNotification: NSDate = fireDate

        

        let notification = UILocalNotification()

        notification.timeZone = NSTimeZone.localTimeZone()

        

        notification.alertBody = "A task has been expired!"

        notification.soundName = UILocalNotificationDefaultSoundName

        notification.fireDate = fireDateOfNotification

        notification.userInfo = ["UUID": (repeatable.objectID.persistentStore?.identifier)!]        

        UIApplication.sharedApplication().scheduleLocalNotification(notification)

    }

 

Continue reading
Rate this blog entry:
0
2161 Hits 0 Comments

Architecture & Conclusions

Architecture

As for the architecture of Repeat the Model-View-Controller (MVC) pattern was implemented. To achieve that, the views were programmed separate from the view controller, and elements like the date pickers were designed as single responsibility objects with an API to change the name, date and the restrictions of the date. The same is true for almost all elements in the view, for example the table view cells, the name and the timespan elements. 

The database is separated by design in iOS. Business logic like date calculation, persistence or notification handling are distinct from each other. Lastly the view controllers bring together the views and the logic. Consequently there are three view controllers responsible for the main screen, the edit screen and the map screen. It was made sure, that every component was decoupled from the rest of the code as much as possible. The Database class is the only place where model objects can be created, stored and changed and the notifications class is responsible only to create, modify and delete notifications. The same is true for all the classes. Repeats architecture is shown in the image below.

Bildschirmfoto 2016 01 23 um 21.56.12

Final Views

Repeat consist of only 3 screens in total as shown in the image below. The main screen, the edit screen and the map screen. Each screen has a distinct purpose. The main screen displays an overview over all currently running tasks and gives control over these. It makes it possible to reset a task as well as create and edit them. The edit screen displays the details of a task and permits editing its content. Lastly, the map screen makes it possible to set an area on the map to restrict the notifications for the currently edited task.

 

Bildschirmfoto 2016 01 23 um 21.44.40

Conclusion

In summery it can be said, that the architecture of Repeat is separated in distinct small parts with single responsibility. Additionally this is true for the UI of Repeat which not only looks beautiful but is designed to let the user only do the things right. In the future we plan to expand Repeat to make it even more useful to the user.

Besides the technical goals, our goal was to improve our personal technical knowledge. We wanted to learn the swift programming language and how to use the tools provided by Apple to expand our horizon. The decision to realize Repeat without prior knowledge about swift helped us to learn a new programming language from scratch.

Continue reading
Rate this blog entry:
0
2064 Hits 0 Comments