Secrets of a successful update: interface, backend, application structure

The long-awaited New Year holidays are approaching - travel time. In this regard, today we would like to talk about working on our joint project with Travel And Play - Webcam World View . The application allows you to connect to streams from video cameras installed in different parts of the world and watch the life boiling there in real time.

Great, right? It seemed to us that way too, until this autumn the task was set for a new, improved version of the product. What exactly to improve was at our discretion, so we immediately decided to leave the functionality alone: ​​it was quite sufficient and at the same time not overloaded. But the UX component and technical performance have undergone a significant revision. About how we made candy from an initially good application, read below.




First, we analyzed the current state of affairs. As it turned out, the application had serious flaws:

- The most, perhaps, the biggest inconvenience was that all the content was contained in the application itself. This method of storage excluded the possibility of editing and updating data without updating the product itself on the market. In addition, this significantly increased the size of the application.

- The application was extremely inconvenient to use. For clarity: to view any of the streams, you had to go through 4 screens and five times press the button on the Apple TV remote.

“This virtual journey was accessible only to Apple TV users, and this is not the largest part of society.”

Having comprehended these problems, we began to look for ways to solve them and as a result we set ourselves several goals:

1) Change the interface, make it more intuitive;
2) Modify the server side so that it not only stores all our content, but also provides statistics, which would allow us to at least highlight popular and top cameras;
3) Well, of course, adapt the application for the iPhone and iPad, that is, make virtual travels accessible to much more people.

The goals are set - the time has come to act.

Create a user-friendly interface


Who knows best how to make a convenient and beautiful application for users of devices from Apple? Of course, Apple. The solution we were looking for lay on the surface, or rather, on the main screen of any Apple TV — we are talking about their proprietary Films application. We wanted to bring everything as close as possible to this model, because our product has similar content, and the manufacturer’s UX solution is familiar to everyone.

What does the user see first when he enters the “Movies” application? Screen with collections and top movies. In Webcam World View, we wanted to do roughly the same thing - all the best cameras on the main screen. Therefore, the next step was to create an analog of the Apple “carousel” on the main screen.



So, we needed to get an automatically moving carousel, the elements of which would have a parallax effect. The basis was the UICollectionView class.

First, a class was created for the cell of our future carousel:

class TopCollectionViewCell: UICollectionViewCell {
// наполнение нашей ячейки — превью для камеры
   @IBOutlet weak var backImageView: UIImageView!
   override func awakeFromNib() {
        super.awakeFromNib()
        backImageView.contentMode = .scaleAspectFill
        backImageView.clipsToBounds = true
        backImageView.layer.cornerRadius = CGFloat(10.0)        
    }
  override func prepareForReuse() {
        super.prepareForReuse()
    }

At the moment when the focus moves to the carousel element, we shift the cell down to the height of the indentation, which we want to get as a result (given value ledgeHeight):

  override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        if (self.isFocused)
        {
		// анимируем движение ячейки вниз, когда элемент в фокусе
            UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseOut, animations: {
                self.transform = CGAffineTransform(translationX: CGFloat(0.0), y: ledgeHeight)
                }, completion: nil)
		// эффект параллакса
            self.backImageView.clipsToBounds = true
            self.backImageView.adjustsImageWhenAncestorFocused = true
        }
        else
        {
		// возвращаем элемент на исходную позицию
            UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseOut, animations: {
                self.transform = CGAffineTransform(translationX: CGFloat(0.0), y: CGFloat(0.0))
                }, completion: nil)
            self.backImageView.clipsToBounds = true
            self.backImageView.adjustsImageWhenAncestorFocused = false
        }
    }

Thus, the carousel element has a parallax effect, and when you switch to focus, it takes the same style as the Apple carousel elements.

Then followed another task - the implementation of the movement of the carousel. To begin with, we decided on what we want.

First, we decided to make the carousel looped (when moving forward from the last element, go to the first, and vice versa, when moving back from the first element, go to the last).

Secondly, it was necessary to realize the automatic movement of the carousel after a given period of time in case its elements were out of focus.

To ensure the possibility of looping the carousel, we added a “buffer” of two cells that corresponded to the first and last element.

Navigating through the "internal" elements was standard, as between UICollectionView elements. The transition from the "external" elements (the first and last) occurred with a shift in content:

 func jump(direction: Direction) {
// direction — направление сдвига, вперед или назад
        let currentOffset = self.contentOffset.x
        var offset = CGFloat(count) * (collectionViewLayout as! Layout).totalItemWidth
        if case .backward = direction {
            offset *= -1
        }
        self.setContentOffset(CGPoint(x: currentOffset + bffset, y: self.contentOffset.y),
                              animated: false)
    }

For the automatic carousel movement, a simple timer was used and, again, the content was shifted from the beginning by the desired amount:

if let initialOffset = (self.collectionViewLayout as! Layout).offsetForItemAtIndex(index: item) {
            self.setContentOffset(CGPoint(x: initialOffset, y: self.contentOffset.y), animated: animated)
 }

So we got a pretty Apple-style carousel with automatic movement.

Then we moved on to filling the main space of the main screen.

It was decided to break the content into collections, they are also “lines”, similar to those that we see in the “Movies” application. Accordingly, the question arose: how to implement them? The problem was that we needed the ability to navigate both through collections (scrolling in the vertical direction) and by cameras in the selected collection (scrolling in the horizontal direction)

To solve this problem, we use TableView, in each cell of which we have a custom CollectionView. The main controller acts as a delegate and data source:



To be able to distinguish between collections of different table cells, use the tag property - write the cell number of the table in which the collection is located.

To set the delegate, data source and current collection number in the table cell, add the setCollectionViewDataSourceDelegate function:

class LineTableViewCell: UITableViewCell {
    //заголовок подборки
    @IBOutlet weak var lineNameLabel: UILabel!
    //коллекция для подборки
    @IBOutlet weak var lineCollectionView: UICollectionView!
    override func awakeFromNib() {
        super.awakeFromNib()
	   ...
    }
}
extension LineTableViewCell {
    func setCollectionViewDataSourceDelegate
        >
        (dataSourceDelegate: D, forRow row: Int) {
        lineCollectionView.delegate = dataSourceDelegate
        lineCollectionView.dataSource = dataSourceDelegate
        lineCollectionView.tag = row
        lineCollectionView.reloadData()
    }
}

Now it remains to call it in the willDisplay method of the table:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let tableViewCell = cell as? LineTableViewCell else { return }
        tableViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: indexPath.row)
    }

Adding the standard delegate methods UITableView and UICollectionView, we got what we need - the content was divided into collections. We used the same trick with the collections in the cells of the table when working on the screen for viewing collections by category.

The implementation of elements of other screens was standard and did not cause problems.

Server side


So, the interface has been successfully implemented. Now it's time to talk about working on the server side of our application. Here the idea was as follows: to make the application as customizable as possible using the server.

All information about each camera was collected on the server. For convenient moderation of content, we wrote the Admin Panel. Using this system, the moderator can configure and at any time change the information about the cameras, make the cameras paid or free, change their places and turn them off if they do not work. Any marketing move can be made without the participation of the developer.

Another feature of our Admin Panel was the ability to customize the appearance of the interface from the server. There are two types of data display in the application:

1. Carousel with the most popular cameras.
2. Rulers with cameras - groups of cameras that are lined up in a horizontal line and united by one idea.

When adding several cameras from the server, the moderator can combine them into a ruler manually or automatically, according to certain tags that are added for each camera.

If there is a need to change the displayed content or the location of the rulers, we just need to drag this or that element to the desired location.

The jScrollPanel plugin was used to create this block. jQuery + CSS - and no magic.

In addition, in the rulers themselves, any camera can be moved horizontally, which allows you to set the sequence taking into account the data that the moderator receives from the application (number of views, transitions on screens, etc.). All changes made at the same time will be displayed in the application.



Admin Panel allows you to define popular cameras based on the collected statistics, automatically creates, updates them and moves them inside the corresponding rulers.

However, not all streams are always available: the server stops working, technical work is carried out, sometimes the cameras turn off for a certain time, and sometimes they stop working forever. In order not to show the user non-working streams and at the same time not to check all cameras manually every day, we taught the system once a day to bypass all streams in automatic mode. If the camera is unavailable, its operation in the application is suspended, and at the same second information about this is sent to the moderator.

Timelapse


In the end, I would like to share another solution found while working on the latest update. Each self-respecting application has its own page on social networks . But displaying only pictures there is boring, and the specifics of our application hints that video content needs to attract the audience. Preview videos didn’t work for several reasons. So the idea was born to make an action video for each of the streams - Timelapse.

Here's what our final recipe looks like, creating very nice things:

- We take a picture from the camera and record a video for five seconds every hour for 24 hours.
- Then we speed up the playback twice, glue all the records into one and get an interesting action-cutting - for example, in our time-lapse video you can watch a bewitching sunset or even a whole day from the life of a tiger.

Future plans


Summarize. We managed to create a system with which it is convenient to work even for a moderator who does not understand anything in programming. Now our team members can lead the project without delving into the intricacies of development and without suffering from the need to update the application on the market each time.

As a result, we got a solid finished product for Apple TV, in which we solved all the tasks. The main achievement for our team was the successful implementation of the server part and administration capabilities, the bonus was the creation of the most user-friendly interface according to the canons of UI Apple TV.

From this:




We made it:





Of course, not everything works without failures and not all plans for the server are currently implemented. The code, in principle, cannot be perfect, so we try not to stand still, constantly working on improvements. Now we have plans to implement on the server settings not only content, but also the full visual component. How many cameras will be displayed in the ruler on the screen, will it be a camera or Timelapse, will each camera have a signature, or only some? All questions to the server.



We are also considering improving the appearance of the Admin Panel. This is not to say that now she is uncomfortable to use or something hurts her eye, but she always wants to strive for possible perfection.

We will be glad to read your reviews. That's all, thanks for watching!

Also popular now: