Solving Min Stack Problem using Swift

It’s been a while since I last posted an article on an algorithm and data structure problem. This time I decided to solve the Min Stack problem. In Min Stack you need to find the minimum element inside a stack in constant time. An alternate question is Max Stack where you have to find the maximum element in a stack. Both are pretty similar conceptually.

The problem explicitly states that each stack method should run at constant time – O(1).

This is the problem statement:

Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.

  • push(x) — Push element x onto stack.
  • pop() — Removes the element on top of the stack.
  • top() — Get the top element.
  • getMin() — Retrieve the minimum element in the stack.

 

Before continuing, make sure you try this problem yourself.

We will create a class calling it MinStack and inside it we will have two arrays.

private var elements: [Int] = []
private var minElements: [Int] = []

The elements array will be our main stack where the user appends or pushes new elements into. The minElement array is going to act as a secondary stack which will be used to keep track of the minimum element value at different stages.

Let’s code the first method – push(x):

func push(_ x: Int) {
        elements.append(x)
        if minElements.count == 0 {
            minElements.append(x)
        }else {
            if let minElement = minElements.last, let lastElement = elements.last {
                if lastElement <= minElement {
                    minElements.append(lastElement)
                }
            }
        }
    }

In this method, we are appending or pushing the element passed in by the function argument to the main ‘elements’ array. Then if the minElements array is empty we are simply appending the passed element into it. This is to “warm up” both stacks.

The next time the user pushes an element, we will simply append it in the elements array however, we will apply some bit of logic before appending it to the minElements stack.

We are using last() method which comes by default with Array. This method returns the last element in the array. The element type returned is optional (if array is empty, nil will be returned). Therefore, we are safely unwrapping last elements from both arrays using If – let statements.

Once we get the elements, we are checking if the top element in the element stack is less than or equal to the top element in the minElement stack. If it is, that element is appended or pushed to minElements stack as well.

Next, let’s implement pop() method:

In this method we will be removing the top element in the stack. We will use the .popLast() method. This method removes the last element in the array and also returns that element.

func pop() {
        let popElement = elements.popLast()
        if let poppedElement = popElement, let minElementPop = minElements.last {
            if poppedElement == minElementPop {
                minElements.popLast()
            }
        }
    }

We grab the returned last element from the main elements stack, and then again check the top element of minElements stack and check if the popped element and the top element of minElements stack is equal or not. If they are equal, then the top element is removed from minElements stack as well.

The remaining two methods are top() and getMin() – in both these methods we are simply popping the last element in the arrays and returning them.

    func top() -> Int {
        return elements.last ?? 0
    }
    
    func getMin() -> Int {
        return minElements.last ?? 0
    }

The main thing here to notice is that the methods run at constant time. Nowhere inside any method did we iterated through the entire array – (this will be O(n)). With O(1) time complexity our algorithm works at constant time and not dependent to the size of the stack.

Getting started with FaceID/TouchID and LocalAuthentication using Swift

Since 2013, all iPhones have been launched with some sort of biometric sensors. From iPhone 5s to iPhone 8, Apple embedded a Touch ID sensor in the home button and from iPhone X onwards they started using Face ID to detect user’s face before unlocking the device.

Apple provides APIs for developers to use these security features in their apps as well. Today I will talk about how to go about setting up biometric security features in your iOS app.

Before starting, make sure you open Info.plist file in Xcode and enter a new row for key “NSFaceIDUsageDescription” and enter a string value for this key. The value should talk about the reason you are using Face ID.

For Touch ID users, the reason is added within the code itself and you don’t need to do it in Info.plist.

Make sure you import LocalAuthentication framework.

So you first do this:

import LocalAuthentication
let context = LAContext()
    var error: NSError?
    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
        let reason = "Touch ID identification needed" // For touch ID only

        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {
            [weak self] success, authenticationError in

            DispatchQueue.main.async {
                if success {
                    // success 
                } else {
                    // error
                }
            }
        }
    } else {
        // no biometry on your device
    }

 

So we are first creating an instance of LAContext() and  checking if the user’s device even has biometry (Touch ID or Face ID).

If yes, then the system checks if the Face ID or Touch ID is correctly identified and verified. If this is the case we get in to the closure with two parameters – “success” and “authenticationError“.  We can use success bool value to then execute any code that we want to get executed if the Face ID / Touch ID has worked correctly. If the Touch ID or Face ID thinks the input is wrong then we can do any error handling inside else block.

Make sure that what ever UI based code you execute, should be done on main thread. Therefore, we are using DspatchQueue.main.async {} when executing the if – else statement.
That’s about it! In order to test Face ID /Touch ID in Xcode Simulator – go to Hardware -> TouchID/FaceID -> Enrolled State. Then when your app asks you for identification, head back to Hardware -> Touch ID/FaceID -> Matching or non Matching to test our the above code.

WWDC Guide – Traveling, lodging, Scholarship orientation & lots more! (Part 2)

WWDC2019 scholarship results were announced earlier this week and I would like to congratulate everyone who got selected 🎉. For those who were unable to get the scholarship, don’t stress out and continue working, grinding and learning. Make sure to try next year.

This is going to be part 2 of WWDC series where I will talk about the traveling, lodging, other events that are going on along side WWDC in San Jose,  and more.

As you may tell from my Instagram, I love traveling. So let’s talk about it! ✈️

San Jose, California 🇺🇸

San Jose, a major tech hub in California’s bay area is where Apple is holding its World Wide Developer Conference since 2017. Getting there is a bit of a challenge if you are traveling from Asia.

My travel itinerary was this:

✈️ Isb -> Abu Dhabi -> Los Angeles -> San Jose ✈️

I took Etihad Airways, which took me from Islamabad to Abu Dhabi. This was a 3 hour flight. Then a 3 hour stay in Abu Dhabi after which I had to take a connecting flight to Los Angeles. This flight is long…. like really long. Around 16.5 hrs long. From there I had around 2 hour stay after which I had to take another flight to San Jose. This was a short 50 minute flight.

Around 20 hours of flying, 2 connections, and good bit of jet lag later, I reached San Jose on Saturday at around 7 pm.

Another option could be to fly to San Francisco, and then take Uber/Lyft/Caltrain to San Jose.

Lodging:

Apple provides free lodging to the scholarship students in San Jose State University dorm rooms. The university is situated at a walking distance from the McEnery convention center.

Scholarship Orientation Ceremony:

Last year, Apple held the orientation ceremony at Steve Jobs Theatre for the very first time. The venue was kept a secret till the very end. I remember while we were walking towards the convention center on Sunday to get our badges etc we all were talking about the “secret location”.  We all were hoping it to be Steve Jobs Theatre and as it turned out; it was.

We got on the buses and went to Steve Jobs Theatre; It was an experience of a lifetime. I am pretty sure that this year Apple will probably do the same and hold the scholarship orientation at Steve Jobs Theatre as well. It’s an absolutely glorious piece of architecture. Simply breathtaking.

Other Events:

Make sure to download Parties for WWDC app. This app lists all the events that will take place around San Jose during WWDC week. It even lists the timing, dates, and ticket information. These events start usually at around evening, around 5-6pm and I highly recommend to go to these events for networking and lots of fun.

Make sure you guys go to The Talk Show. A show hosted by John Gruber. It takes place in California Theatre on Tuesday at around 7pm. The tickets are made available around a week prior to the WWDC week. Tickets go out in a flash so you gotta be really quick to grab one.

Last year, Gruber was joined by Greg Joswiak and Mike Rockwell. They talked about all the WWDC announcements and it was absolutely incredible.

Highly recommended! 💯

Tips:

  1. Make sure to network and socialize as much as you can.
  2. 👨🏻‍💻 Start a project and take it with you to WWDC. Ask Apple engineers and other developers about any issues/features etc
  3. 👩🏼‍💻Make sure you attend the labs and ask Apple engineers about any development issues you are having. Labs are exceptionally useful. Avail them as much as you can!
  4. Take it slow. There is no need to attend all the sessions. You can watch them later. Sit down in the scholarship lounge, chat with other scholars, ask about their playgrounds and just enjoy the moment.
  5. 🥤Did I mention Odwalla?

Making a Tinder-esque Card Swiping interface using Swift

 

Tinder – we all know that dating app where you can just reject or accept someone by swiping right or left. BAM! The whole card swiping idea is now used in tons of apps. It’s a way to show data if you have grown tired of using table views and collection views. There are tons of tutorial on this and this project took me good bit of time.

You can check out the full project on my Github.

First of all, I would like to give credit to Phill Farrugia’s medium post on this and then Big Mountain Studio’s YouTube series on a similar topic. So how do we go about making this interface. I got quite a lot of help from Phill’s medium post on this front. Basically the idea is to create UIViews and insert them as subviews in a container view. Then using the index we will give each UIView some horizontal and vertical inset and tweak its width a bit. Then when we swipe away one card, all the views frames will be rearranged according to the new index value.

We will start by creating a container view in a simple ViewController.

class ViewController: UIViewController {

    //MARK: - Properties
    var viewModelData = [CardsDataModel(bgColor: UIColor(red:0.96, green:0.81, blue:0.46, alpha:1.0), text: "Hamburger", image: "hamburger"),
                         CardsDataModel(bgColor: UIColor(red:0.29, green:0.64, blue:0.96, alpha:1.0), text: "Puppy", image: "puppy"),
                         CardsDataModel(bgColor: UIColor(red:0.29, green:0.63, blue:0.49, alpha:1.0), text: "Poop", image: "poop"),
                         CardsDataModel(bgColor: UIColor(red:0.69, green:0.52, blue:0.38, alpha:1.0), text: "Panda", image: "panda"),
                         CardsDataModel(bgColor: UIColor(red:0.90, green:0.99, blue:0.97, alpha:1.0), text: "Subway", image: "subway"),
                         CardsDataModel(bgColor: UIColor(red:0.83, green:0.82, blue:0.69, alpha:1.0), text: "Robot", image: "robot")]
    var stackContainer : StackContainerView!
  
    
    //MARK: - Init
    
    override func loadView() {
        view = UIView()
        view.backgroundColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0)
        stackContainer = StackContainerView()
        view.addSubview(stackContainer)
        configureStackContainer()
        stackContainer.translatesAutoresizingMaskIntoConstraints = false
        configureNavigationBarButtonItem()
    }
 
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Expense Tracker"
        stackContainer.dataSource = self
    }
    
 
    //MARK: - Configurations
    func configureStackContainer() {
        stackContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        stackContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -60).isActive = true
        stackContainer.widthAnchor.constraint(equalToConstant: 300).isActive = true
        stackContainer.heightAnchor.constraint(equalToConstant: 400).isActive = true
    }

As you can see I have created a custom class called SwipeContainerView and just configuring the stackViewContainer using the auto layout constraints. Nothing too bad. The SwipeContainerView will be 300 x 400 in size and it will be centered in X axis and just 60 pixels above the middle of Y axis.

Now that we have set up the stackContainer, we will head over to the StackContainerView subclass and load all the card views in it. Before doing that we will create a protocol that will have three methods:

protocol SwipeCardsDataSource {
    func numberOfCardsToShow() -> Int
    func card(at index: Int) -> SwipeCardView
    func emptyView() -> UIView? 
}

Think of this protocol as a TableViewDataSource. Conforming our ViewController class to this protocol will allow us to transfer information of our data to the SwipeCardContainer class. The three methods we have in this are:

  1. numberOfCardsToShow() -> Int : Returns the number of cards we need to show. This is just the count of the data array.
  2. card(at index: Int) -> SwipeCardView : Returns the SwipeCardView(we will create this class in just a moment)
  3. EmptyView -> Not going to do anything with this but once all the cards have been swiped out, call to this delegate method will return an empty view with some sort of message (I will not implement this in this particular tutorial, give it a try! )

Conform the view controller to this protocol:

extension ViewController : SwipeCardsDataSource {
    func numberOfCardsToShow() -> Int {
        return viewModelData.count
    }
    
    func card(at index: Int) -> SwipeCardView {
        let card = SwipeCardView()
        card.dataSource = viewModelData[index]
        return card
    }
    
    func emptyView() -> UIView? {
        return nil
    }   
}

In the first method, return the number of items in the data array. In the second method, create a new instance of SwipeCardView() and send the array data for that index and then return the SwipeCardView instance.

SwipeCardView is a subclass of UIView on which there is a UIImage, UILabel, and a gesture recognizer. More on this later. We will use this protocol to communicate with the container view.

stackContainer.dataSource = self

When the above code is triggered, this will trigger a function of reloadData that will then call these datasource functions.

Class StackViewContainer: UIView {
.
.

  var dataSource: SwipeCardsDataSource? {
        didSet {
            reloadData()
         }
    }

....

The reloadData function:

   func reloadData() {
        guard let datasource = dataSource else { return }
        setNeedsLayout()
        layoutIfNeeded()
        numberOfCardsToShow = datasource.numberOfCardsToShow()
        remainingcards = numberOfCardsToShow
        
        for i in 0..<min(numberOfCardsToShow,cardsToBeVisible) {
            addCardView(cardView: datasource.card(at: i), atIndex: i )
            
        }
    }

In this reloadData function, we are first getting the number of cards and storing it in numberOfCardsToShow variable. Then we are assigning that to another variable called remainingCards. In the for loop, we are creating a card which is instance of SwipeCardView using the index value.

 for i in 0..<min(numberOfCardsToShow,cardsToBeVisible) {
            addCardView(cardView: datasource.card(at: i), atIndex: i )
         }

We essentially want less than 3 cards to appear at any one time. Therefore we are using min function. CardsToBeVisible is a constant at 3. If the numberOfToShow is more than 3 then only three cards will be rendered. We are creating these cards from protocol method of:

func card(at index: Int) -> SwipeCardView

The addCardView() function is just used to insert the cards as subviews.

  private func addCardView(cardView: SwipeCardView, atIndex index: Int) {
        cardView.delegate = self
        addCardFrame(index: index, cardView: cardView)
        cardViews.append(cardView)
        insertSubview(cardView, at: 0)
        remainingcards -= 1
    }

In this function we are adding the cardView to the view hierarchy and as we are adding the cards as subview we are decrementing the remaining cards by 1. Once we have added the cardView as the subview, we then set the frame of those cards. For this we are using another function of addCardFrame():

 func addCardFrame(index: Int, cardView: SwipeCardView) {
        var cardViewFrame = bounds
        let horizontalInset = (CGFloat(index) * self.horizontalInset)
        let verticalInset = CGFloat(index) * self.verticalInset
        
        cardViewFrame.size.width -= 2 * horizontalInset
        cardViewFrame.origin.x += horizontalInset
        cardViewFrame.origin.y += verticalInset
        
        cardView.frame = cardViewFrame
    }

This addCardFrame() logic is taken directly from Phill’s post. Here we are setting frame of the card according to the index of that card. The first card with index 0 will have the frame directly as that of the container view. Then we are changing the origin of the frame and width of the card according to the inset. So essentially we add the card slightly to the right of the card above, decrease it’s width, also make sure to pull the cards downwards to get that feeling of the cards being stacked on top of each other.

Once this is done, what you will see is cards being stacked on top of each other. Pretty good so far!

However now we need to add the swipe gesture to this card view. Let’s now bring our attention to the SwipeCardView class.

SwipeCardView

The swipeCardView class is just a normal UIView subclass. However, for reasons only known to Apple engineers, it is incredibly difficult to add shadows to a UIView with rounded corner. In order to add shadows to the card views, I am creating two UIViews. One is a shadowView and then on top of that there is a swipeView. The shadowView essentially has the shadow and all. The swipeViewhas rounded corners. On the swipeView I have added UIImageView, a UILabel to showcase the data and pictures.

 var swipeView : UIView!
 var shadowView : UIView!

Configuring shadowView and swipeView

    func configureShadowView() {
        shadowView = UIView()
        shadowView.backgroundColor = .clear
        shadowView.layer.shadowColor = UIColor.black.cgColor
        shadowView.layer.shadowOffset = CGSize(width: 0, height: 0)
        shadowView.layer.shadowOpacity = 0.8
        shadowView.layer.shadowRadius = 4.0
        addSubview(shadowView)
        
        shadowView.translatesAutoresizingMaskIntoConstraints = false
        shadowView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        shadowView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
        shadowView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        shadowView.topAnchor.constraint(equalTo: topAnchor).isActive = true
    }
    
    func configureSwipeView() {
        swipeView = UIView()
        swipeView.layer.cornerRadius = 15
        swipeView.clipsToBounds = true
        shadowView.addSubview(swipeView)
        
        swipeView.translatesAutoresizingMaskIntoConstraints = false
        swipeView.leftAnchor.constraint(equalTo: shadowView.leftAnchor).isActive = true
        swipeView.rightAnchor.constraint(equalTo: shadowView.rightAnchor).isActive = true
        swipeView.bottomAnchor.constraint(equalTo: shadowView.bottomAnchor).isActive = true
        swipeView.topAnchor.constraint(equalTo: shadowView.topAnchor).isActive = true
    }

Then I have added a pan gesture recognizer to this card view and on recognition a selector function is called. This selector function has tons of logic behind swipe, tilting etc. Let’s take a look:

 

    @objc func handlePanGesture(sender: UIPanGestureRecognizer){
        let card = sender.view as! SwipeCardView
        let point = sender.translation(in: self)
        let centerOfParentContainer = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
        card.center = CGPoint(x: centerOfParentContainer.x + point.x, y: centerOfParentContainer.y + point.y)
       switch sender.state {
        case .ended:
            if (card.center.x) > 400 {
                delegate?.swipeDidEnd(on: card)
                UIView.animate(withDuration: 0.2) {
                    card.center = CGPoint(x: centerOfParentContainer.x + point.x + 200, y: centerOfParentContainer.y + point.y + 75)
                    card.alpha = 0
                    self.layoutIfNeeded()
                }
                return
            }else if card.center.x < -65 {
                delegate?.swipeDidEnd(on: card)
                UIView.animate(withDuration: 0.2) {
                    card.center = CGPoint(x: centerOfParentContainer.x + point.x - 200, y: centerOfParentContainer.y + point.y + 75)
                    card.alpha = 0
                    self.layoutIfNeeded()
                }
                return
            }
            UIView.animate(withDuration: 0.2) {
                card.transform = .identity
                card.center = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
                self.layoutIfNeeded()
            }
        case .changed:
             let rotation = tan(point.x / (self.frame.width * 2.0))
            card.transform = CGAffineTransform(rotationAngle: rotation)
            
        default:
            break
        }
    }

The first four lines in the above code:

 let card = sender.view as! SwipeCardView
 let point = sender.translation(in: self)
 let centerOfParentContainer = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
 card.center = CGPoint(x: centerOfParentContainer.x + point.x, y: centerOfParentContainer.y + point.y)

First we get the view on which the gesture has been swiped. Next we are using the translation method to get how much the user has swiped on the card. The third line is essentially getting the mid point of the parent container. The last line is where we are setting the card.center. As the user swipes on the card, the center of the card increases by translated x value and translated y value. In order to get that snapping behavior we are essentially changing the center point of the card from fixed coordinates. When the gesture translation ends we get it back to the card.center.

In state.ended case:

if (card.center.x) > 400 {
                delegate?.swipeDidEnd(on: card)
                UIView.animate(withDuration: 0.2) {
                    card.center = CGPoint(x: centerOfParentContainer.x + point.x + 200, y: centerOfParentContainer.y + point.y + 75)
                    card.alpha = 0
                    self.layoutIfNeeded()
                }
                return
            }else if card.center.x < -65 {
                delegate?.swipeDidEnd(on: card)
                UIView.animate(withDuration: 0.2) {
                    card.center = CGPoint(x: centerOfParentContainer.x + point.x - 200, y: centerOfParentContainer.y + point.y + 75)
                    card.alpha = 0
                    self.layoutIfNeeded()
                }
                return
            }

we are checking if the card.center.x value is greater than 400 or if the card.center.x is less than -65. If it is so then we are dismissing those cards by changing the card.center.

If swiped to the right:

card.center = CGPoint(x: centerOfParentContainer.x + point.x + 200, y: centerOfParentContainer.y + point.y + 75)

If swiped to the left:

card.center = CGPoint(x: centerOfParentContainer.x + point.x - 200, y: centerOfParentContainer.y + point.y + 75)

If the user ends the gesture mid way between 400 and -65 well then we will reset the center of the card.  We are also calling a delegate method when the swipe ends. More on this later.

For getting that tilt when you swipe on the card; I will be brutally honest. I used bit of geometry and used different values of perpendicular and base and then used tan function to get a rotation angle. Again this was just trial and error. Using the point.x and container’s width as two perimeters seemed to work well. Feel free to experiment with these values.

case .changed:
             let rotation = tan(point.x / (self.frame.width * 2.0))
            card.transform = CGAffineTransform(rotationAngle: rotation)

Now lets talk about the delegate function. We will use the delegate function to communicate between the SwipeCardView and the ContainerView.

protocol SwipeCardsDelegate {
    func swipeDidEnd(on view: SwipeCardView)
}

This function will take in a view on which the swipe took place and we will do some steps on it to remove it from the subviews and then redo all the frames for the cards beneath it. Here is how:

    func swipeDidEnd(on view: SwipeCardView) {
        guard let datasource = dataSource else { return }
        view.removeFromSuperview()

        if remainingcards > 0 {
            let newIndex = datasource.numberOfCardsToShow() - remainingcards
            addCardView(cardView: datasource.card(at: newIndex), atIndex: 2)
            for (cardIndex, cardView) in visibleCards.reversed().enumerated() {
                UIView.animate(withDuration: 0.2, animations: {
                cardView.center = self.center
                  self.addCardFrame(index: cardIndex, cardView: cardView)
                    self.layoutIfNeeded()
                })
            }

        }else {
            for (cardIndex, cardView) in visibleCards.reversed().enumerated() {
                UIView.animate(withDuration: 0.2, animations: {
                    cardView.center = self.center
                    self.addCardFrame(index: cardIndex, cardView: cardView)
                    self.layoutIfNeeded()
                })
            }
        }
    }

First remove this view from the super view. Once that is done check if there is any remaingcard left. If there is, then we will create a new index for a new card to be created. We will create the newIndex by subtracting the total number of cards to show with the remaining cards. Then we will add the card as subview. However, this new card will be the bottom most one so the 2 that we are sending will essentially make sure that the frame we will add is corresponding to the index 2 or the bottom most.

In order to animate the frames of the rest of the cards,  we will use the index of the subviews. To do this we will create a visibleCards array that will have all the subviews of the containerView in it as an array.

var visibleCards: [SwipeCardView] {
        return subviews as? [SwipeCardView] ?? []
    }

The problem however is that visibleCards array will have subviews index inverted. So the first card will be third, second will remain in second place but the third card will be in the first position. To prevent this from happening we will run the visibleCards array in reversed manner in order to get the actual index of the subview and not how they are arranged in the visibleCards array.

    for (cardIndex, cardView) in visibleCards.reversed().enumerated() {
                UIView.animate(withDuration: 0.2, animations: {
                cardView.center = self.center
                  self.addCardFrame(index: cardIndex, cardView: cardView)
                    self.layoutIfNeeded()
                })
            }

So now we will update the frames of the rest of the cardViews.

So that’s basically it. This is a perfect way to present small amount of data.

View post on imgur.com

 

My WWDC18 Scholarship Project (Part 1)

Apple just announced WWDC 2019 and I couldn’t be more excited. WWDC is an experience of a lifetime. As an iOS developer and someone who loves software, WWDC gave me a platform to meet and interact with creative and bright developers from around the globe. I learned so many new software tips and concepts from Apple engineers; and it further motivated me to design and create apps for iPhones and iPads.

In this first of two parts series regarding WWDC, I will be talking about my last year’s scholarship submission, how I made it and some useful tips for anyone looking to create Swift Playground for this year’s scholarship. The second part will be all about traveling + packing, lodging, and events that take place around San Jose during WWDC week and all the non technical stuff.

Here is my playground in action:

 

Swift Playground: Passcode

I called my Swift playground, Passcode and it’s available on Github here. I got the idea for this project from iOS itself. I really liked how iOS verifies the passcode as the user enters the last key of the passcode. So I thought why not create a passcode type interface with numbers moving all around the screen and the user has to click the correct circles in correct order to unlock a card about me.

Random Motion of Circles:

The first major thing I had to do was to move the circles all around the screen in a random fashion.  To achieve this first I subclassed UIView (in this it was a UILabel).

public class Circles: UILabel {
        var velocity: CGPoint = .zero
        public var isSelected: Bool = false
        public var num: Int = 0     
    }

The Circles class has three properties, a velocity of type CGPoint – initially X and Y coordinate of which is 0,0. Then there is a ‘isSelected‘ bool variable and then a num which is of type Int.

Next, I made 12 of these circles using a simple for loop:

for var i in 1...12 {

          let randindex = drand48() * 1
          var color : UIColor = .red
           if (randindex < 0.33) { 
              color = color3 
              } else if (randindex > 0.33 && randindex < 0.66) 
              { color = color2 
              } else if (randindex > 0.66 && randindex < 0.99)
              {
              color = color4
            }

         let view = circles(frame: CGRect(x: 200 , y: 200, width: 40, height: 40))
          view.layer.cornerRadius = 20 
          view.layer.masksToBounds = true
          view.isSelected = false
          view.num = i
          view.layer.zPosition = 4
          view.text = "   \(view.num)"
          view.textColor = .white
          view.isUserInteractionEnabled = true
        
          view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap1)))
          view.backgroundColor = color
          view.velocity.x = CGFloat(arc4random_uniform(100) + 50 )/100.0
          view.velocity.y = CGFloat(arc4random_uniform(100) + 50)/100.0
                
          addSubview(view)
          circleviews.append(view as! circles)

In this for loop, I am looping through the code 12 times. The first bit is randomly picking colors for the view from 4 colors that I have defined initially. Then I am creating the circle view and giving it corner radius, setting the .isSelected value to false, giving it num of the current index, i and also the same label text. Then I am adding a gesture recogniser to each view that I am creating. Similarly, I am giving the velocity property of each view a random X and Y value. Once the view has been created and properties set, I am adding it as a subview and appending it to an array called circleviews.

Next I am setting up a CADisplayLink – CADisplayLink is essentially a timer that triggers a function at exact same rate as your display’s refresh rate. This allows for smooth animations.

startTime = CACurrentMediaTime()
let displayLink = CADisplayLink( target: self, selector: #selector(movecircle))
displayLink.add(to: .main, forMode: RunLoop.Mode.common)
self.displayLink = displayLink

Here I am creating a CADisplayLink and each time when it gets triggered, I am calling a movecircle selector function.

@objc func movecircle(_ displayLink: CADisplayLink) {
        var elapsed = CACurrentMediaTime() - startTime
        
        if elapsed > animationLength {
            stopDisplayLink()
            elapsed = animationLength
        }
        for circle in circleviews {
            if (!circle.isSelected)
            {
            circle.center.x += circle.velocity.x
            circle.center.y += circle.velocity.y
            
            if (circle.center.x > 490 || circle.center.x < 0 ) 
             { circle.velocity.x *= -1.0 
             } else if(circle.center.y > 490 || circle.center.y < 150)
            {
                circle.velocity.y *= -1.0
            }
            }
        }
    }

In the movecircle function, initially I am checking for the elapsed time and if elapsed is greater than the animationLength which I set earlier, I stop the animation. If not then in for loop I traverse the circleview array and if the circleview is not isSelected, then I change the circle.center.x to the circle.velocity.x / same with Y coordinate. The circle.velocity.x is randomly generated at the beginning when the view is created so in essence the circle in it’s entire life time goes on a same path it’s just different from other circles around it that gives it the impression of the circles going randomly. Then in the if statement, I am checking if the circle.center.x and circle.center.y has not passed the the superview edges, and if they have then the velocity is reversed by multiplying it with “-1”.

Which Circle should Go Where?

So this is how the circles are moved around the screen in random motion. Next we will talk about how when I click on the circle, it moves to a specific baseCircle and how it knows which one should go where.

Before starting lets talk about the 4 white base circles that always has a random number on it between 1 and 12. These base circles always have a different number to each other and the numbers that been done in previous tries do not appear again. More on this later. Now say that the base circles have been configured and each have different numbers on them. Now when you click the moving circles, you want the circles to move towards these base circles in order of tapping. The circle tapped first goes to the first base circle, the circle tapped second moves to the second base circle and so on. Also if the circle that reaches the base circle turns out to be wrong then the next circle should move to the same location as the previous one did.

This whole thing sounds really complicated but it really isn’t. When the colored circle is tapped, handleTap1 function is triggered.

@objc func handleTap1(sender: UITapGestureRecognizer) {
        let viewtapped = sender.view! as! circles
        viewtapped.isSelected = true
        count = count + 1
        switch count {
        case 1:
            
            interactionDisabledCircle()
            UIView.animate(withDuration: 3, delay: 0.0, options: [], animations: {
                viewtapped.center = CGPoint(x: 170, y: 95) //self.baseCircles[self.count - 1].center
            }, completion: { (finished: Bool) in
                if (viewtapped.num == self.baseCircles[self.count - 1].baseNum)
                {
                   
                    
                    self.playSound()
                    self.correctPlayer?.play()
                    
                    self.circleSet.append(viewtapped as! circles)
                    self.turncount = self.turncount + 1
                    self.interactionEnabledCircle()
                } else {
                    
                    viewtapped.isSelected = false
                    
                    DispatchQueue.global(qos: .background).async {
                        self.playWrongSound()
                        self.player?.play()
                    }
                    viewtapped.center = self.randomCoordinates()
                    self.count = self.count - 1
                    self.interactionEnabledCircle()
                }
            }).....

Now I will just list 1 out of the 4 cases here but they all are pretty similar. When the first colored circle is tapped, a count variable increments, and the circle.isSelected property is set true. In the switch case I am checking the count variable. If it’s 1 that means it’s the first circle that the user tapped. Next, we animate the movement of the circle as the circle.center moves to the baseCircle.center. Before all this I am disabling any gesture recognition on the moving circles so that it doesn’t become a mess. Once the animation is completed, in the completion handler, I am checking if the number on the colored circle is same as the one on the baseCircle. If it is then a correct sound is played and the tappedview / or the colored circle is added to a circleSet array which are then later removed as the game continues and then I enabled all the interactions. However, if the  number is not same on the baseCircle and the colored circle then the .isSelected property is set to false, a wrong sound is played and the center of the colored circle is given a random center coordinate and the count which was initially incremented is decremented.

This is how the circles know exactly where they need to go.

BaseCircles Situation:

Now as for the baseCircles, how the baseCircles know which numbers are already done and how I reload them once all the 4 circles have been correctly filled?

The baseCircles are made in similar fashion as the coloredCircles, only thing which is different is that for each of the 4 circles that are created, the number on them are checked before being added:

  var rn = randomNumber()           
  while(baseNumbersArray.contains(Int(rn)))
  {
   rn = randomNumber()
   }
   base.baseNum = Int(rn)
   baseNumbersArray.append(Int(rn))

In the for loop in which I am creating these random circles, I am generating a random number between 1 – 12 and initially checking if its in baseNumberArray which is initially empty but the generated number is appended to the array as more and more base circles are created. If the rn, or random number exists in the array then a new randomNumber is generated until a new once comes about which is not present in the baseNumbersArray.

So thats essentially how this works. To show cards that appear after the 4 circles are correctly filled, all I am doing is running a function at case 4 of the switch statement in handleTap1 function which I talked about earlier.

So this was my project. You can get the entire code for this on my GitHub.

Tips:

  • Make sure to use latest Apple technologies but it’s not compulsory. I did not use any latest iOS technologies in this project. However it will surely help
  • Keep it simple.
  • It’s all about creativity. Express yourself in the Swift Playground. Don’t worry if you think it’s way too simple or novice. If you think it will work well then go for it. It’s all about how you present your idea.
  • Enjoy the process!

Part 2: Will be all about traveling, lodging, the conference, WWDC parties, San Jose, Steve Jobs Theatre & lots more!

Creating Pull to Dismiss / Pull to Reach animation using Swift

One of my most used app on my iPhone is Bear app. It’s a beautiful and simple notes-taking app with tons of neat UI / UX features. One of which is being able to pull down on a scroll view to dismiss a view or select a button. As phones are getting taller, buttons on the top navigation bar are getting difficult to access using just one hand. Such gestures help users access these buttons just by swiping down or scrolling down on a scroll view which is embedded in collection view or tableviews by default.

Not only accessing navigation items, you can dismiss views containing scroll views by similar gestures. I decided to create such an animation and in this post I will take you through the entire process of analyzing the animation and then building it from scratch.

Complete project is available on GitHub. I have also incorporated protocol / delegate so that I can communicate between the pop up view and the controller. More on this later.

The project also contains some code through which I am setting up the view controller, applying dark view on the background and animating the pop up view as the user taps on the “Action” button in the top navigation bar. We are going to dismiss this view by using scroll view which is embedded in that view.

I am creating the pop up view in a separate file that goes by the name of ActionMenu. I have wrapped it in a NSObject subclass. The entire animation of the pop up view appearing from the bottom of the screen, creating the UI components and the entire code for this particular animation is written in this NSObject class file. Through the use of encapsulation, we are creating an instance of this class in the view controller and calling the configureActionMenu() and showActionMenu() functions when the “Action” button is tapped.

The code in the UIViewController file to call the ActionMenu when the action button is tapped.

  lazy var actionMenu : ActionMenu = {
        let menu = ActionMenu()
        menu.delegate = self
        return menu
    }()

and then in the selector function of handleActionButton():

  @objc func handleActionButton() {
        actionMenu.configureActionMenu()
        actionMenu.showActionMenu() 
    }

Now lets get into the real animation code. I have embedded a scroll view in the action view. I am also conforming the ActionMenu class to the UIScrollViewDelegate so that I can use some Scroll View delegate methods.

The first method we will use is this:

func scrollViewDidScroll(_ scrollView: UIScrollView) {}

So when ever you scroll the scrollView this method will be called.  Now before we go any further let’s dissect this animation. The highlight animation works with having two buttons stacked on top of each other. The button on the foreground has a mask layer on it. Remember: When you have a masklayer assigned to a UIView layer then that view alpha becomes 0. So essentially when this mask layer is on the foreground button we can only see the button behind it. Next we will animate this mask so that it goes away as the user scrolls so that we can see the foreground button.

So first set up the two buttons and apply the constraints:

   lazy var cancelButton : UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("✕", for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 18)
        button.backgroundColor = .white
        button.setTitleColor(.gray, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(handleCancelButton), for: .touchUpInside)
        button.layer.cornerRadius = 15
        button.clipsToBounds = true
        return button
    }()
    
    lazy var cancelButtonForeground : UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("✕", for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 18)
        button.backgroundColor = .red
        button.setTitleColor(.white, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(handleCancelButton), for: .touchUpInside)
        button.layer.cornerRadius = 15
        button.clipsToBounds = true
        return button
    }()
    
    let maskLayer = CAShapeLayer()

Constraints for the views:

        actionView.addSubview(cancelButton)
        cancelButton.rightAnchor.constraint(equalTo: actionView.rightAnchor, constant: -8).isActive = true
        cancelButton.topAnchor.constraint(equalTo: actionView.topAnchor, constant: 10).isActive = true
        cancelButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
        cancelButton.widthAnchor.constraint(equalToConstant: 30).isActive = true
        
        actionView.addSubview(cancelButtonForeground)
        cancelButtonForeground.rightAnchor.constraint(equalTo: cancelButton.rightAnchor).isActive = true
        cancelButtonForeground.topAnchor.constraint(equalTo: cancelButton.topAnchor).isActive = true
        cancelButtonForeground.leftAnchor.constraint(equalTo: cancelButton.leftAnchor ).isActive = true
        cancelButtonForeground.bottomAnchor.constraint(equalTo: cancelButton.bottomAnchor).isActive = true

Note how I have created a maskLayer of class CAShapeLayer. I will assign this layer to the cancelButtonForeground.

       cancelButtonForeground.layer.mask = maskLayer
       cancelButtonForeground.layer.masksToBounds = true

Now let’s get back to the UIScrollView method of scrollViewDidScroll() :

 func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let goal : CGFloat = 80
        let contentOffset =  scrollView.contentOffset.y
        let progress = -contentOffset / goal
        let finalProgress = max(0, min(1, progress))
        updateActionMenu(progress: finalProgress)
}

Now lets see what happening in this method:

First we are setting a goal. How many pixels should the user scroll down for the animation to operate. I have set it to 80. Then we will use scrollView.contentOffset.y to get the value as the person scrolls down. This value is negative, so we will convert it to positive by multiplying it with -1. ContentOffset, put simply,  tells us how much content has the user scrolled. Pulling it downwards provides us with values in negative.

Next we are measuring the progress.  To do this we are dividing the contentOffset by the goal. This should give us value in decimals. Starting from 0 to more than 1. We want to make sure that the progress is between 0 and 1. To do this we will use max and min methods in swift to restrict the value. If the progress value is more than 1, then return value of 1. If the progress is between 0 and 1 then return the progress value.

let finalProgress = max(0, min(1, progress))

min(1, progress) – return the progress value which is minimum. If progress is more than 1 return 1.

max(0, min(1, progress)) – what ever the value we get from the min function, return the max value between it and 0.

Next  we have created a custom updateActionMenu function that takes in the finalprogress as the parameter. This finalprogress will be a CGFloat item between 0 and 1. Now we can use these values to animate the mask layer.

func updateActionMenu(progress: CGFloat) {
        let height = cancelButtonForeground.frame.height *progress
        let buttonrect = CGRect(x: 0, y: cancelButtonForeground.frame.height, width: cancelButtonForeground.frame.width, height: -height)
        let mask = UIBezierPath(rect: buttonrect)
        maskLayer.path = mask.cgPath
      ...

This updateActionMenu function gets the progress value. We use that progress value to create a path for the mask. Currently our mask has no path so therefore it is hiding the foreground button fully. We will give it a path by constructing it using UIBezierPath.  We will create it using CGRect, and change the height of the rect using the progress value.

let height = cancelButtonForeground.frame.height *progress

Use this height when create the buttonRect.

let buttonrect = CGRect(x: 0, y: cancelButtonForeground.frame.height, width: cancelButtonForeground.frame.width, height: -height)

Create the mask from the UIBezierPath and add that path as .cgPath to the maskLayer.path. Essentially as the height of the mask increase, it will show the UIView layer behind it. When the progress becomes 1, the entire button will be visible.

So with this amount of code, we get this:

 

Hoorayy! 🎉

However, we are still not done. We also need to increase the scale the button as we are scrolling. This means that we have three animation states.

  • notStarted – progress value should be 0 at this point.
  • highlightFull – progress value can be anything at this point – I went for 0.75. By this progress value, the foreground button should be visible.
  • finalFull – progress value should be 1 here. Between highlightFull and finalFull – the scale value of the button should change

Let’s implement these changes, starting with progress. So now we want to completely highlight the button when the progress is 0.75.

 func updateActionMenu(progress: CGFloat) {
        let newgoal : CGFloat = 0.75
        let newprogress = progress / newgoal
        let finalProgress = max(0, min(1, newprogress))
        let height = cancelButtonForeground.frame.height * finalProgress
        let buttonrect = CGRect(x: 0, y: cancelButtonForeground.frame.height, width: cancelButtonForeground.frame.width, height: -height)
        print(buttonrect)
        let mask = UIBezierPath(rect: buttonrect)
        maskLayer.path = mask.cgPath
        ...

Going for a pretty similar logic as before. I am dividing the progress by newgoal of 0.75. The value of newProgress is between 0 and 1. Again I am using the max and min methods to make sure that the max value is 1 and the min is 0.

Then I am using the finalProgress variable to change the height of the mask path. So now by the time 75% of the 80 pixels have been scrolled the highlight animation is fully completed. Now when the progress value is more than 0.75 we want to scale up the button.

    func updateActionMenu(progress: CGFloat) {
        let newgoal : CGFloat = 0.75
        let newprogress = progress / newgoal
        let finalProgress = max(0, min(1, newprogress))
        let height = cancelButtonForeground.frame.height * finalProgress
        let buttonrect = CGRect(x: 0, y: cancelButtonForeground.frame.height, width: cancelButtonForeground.frame.width, height: -height)
        print(buttonrect)
        let mask = UIBezierPath(rect: buttonrect)
        maskLayer.path = mask.cgPath
       
        switch progress {
        case _ where progress >= 0.75 && progress < 1:
            let goalScale : CGFloat = 1.3
            let newProgress = (progress - 0.75) / 0.25
            let scaling = 1 + (goalScale - 1) * newProgress
            cancelButtonForeground.transform = CGAffineTransform(scaleX: scaling, y: scaling)
        case ...0.75:
            cancelButtonForeground.transform = CGAffineTransform.identity
        case 1:
            handleDismissGesture()
        default:
            break
        }
    }

To do this we will use switch statement. In the first case we are checking if the progress is greater or equal to 0.75 and less than 1.

case _ where progress >= 0.75 && progress < 1:

The code that follows is where we are changing the scale.

  let goalScale : CGFloat = 1.3
  let newProgress = (progress - 0.75) / 0.25
  let scaling = 1 + (goalScale - 1) * newProgress
  cancelButtonForeground.transform = CGAffineTransform(scaleX: scaling, y: scaling)

The goal for the scale is 1.3. We want to increase the size of the button 1.3 times on both X and Y axis. Now we want to calculate the scale value as the person scrolls. So first get the progress value which will again be between 0 and 1. This time we are sure it will not cross 1 so there is no point of using min and max methods here.

let scaling = 1 + (goalScale - 1) * newProgress

The scaling is done by subtracting the goalScale (1.3) from 1 and multiplying it with newProgress which will be a value between 0 and 1. Then add 1 to it since we want to enlarge it.  Use the transform property and assign it CGAffineTransform.

In the …case 0.75 block we will make sure that the transform value is .identity or 1. The three … dots that precedes the case means that the progress value should be less than 0.75.

In the last case 1 block, we will call handleDismissGesture() that dismisses the pop up view.

 @objc func handleDismissGesture() {
        guard let window = UIApplication.shared.keyWindow else { return }
        let width = window.frame.width - (padding * 2)
    
        UIView.animate(withDuration: 0.3) {
            self.blackView.alpha = 0
            self.actionView.frame = CGRect(x: self.padding, y: window.frame.height, width: width, height: self.height)
        }
    }

and we are now done! 🎉🎉

This is what we get now:

 

Using UIStackView & Custom UIButton Class to add buttons to TwitterCard Interface (Part 2)

In last week’s post, we created the Twitter card interface and used UIPanGestureRecognizers to drag and dismiss the card. In this article we will take a look at how to set up buttons on the card view.

There are many ways to set up UIButtons in a vertical stack – you can simply use auto layout and individually set up constraints for each button. However, if you notice there are 8 buttons that we need to lay out and setting up constraints for each button will be a pretty tedious task.

UIStackView helps in laying out UI components. It does most of the auto layout stuff itself, so if you want the card to show buttons in both portrait and landscape orientation, the UIStackView will help you in making sure that all the buttons are perfectly visible in both orientations.

We will also be using custom UIButton class in order to create multiple buttons quickly without having to use same code over and over again.

The CustomButton class is pretty simple:

class CustomButton : UIButton {
    
  
    required init(color: UIColor, titleString : String) {
        
        super.init(frame: .zero)
        backgroundColor = color
        setTitle(titleString, for: .normal)
            
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
}

The CustomButton class has a required initializer that takes a color and titleString as arguments. After calling the super initializer and once the self is available, we are setting up the background and the title text of the button.

The button class is all set up. You can customize the button even more here if you want. Next, we will create CustomButton objects in the CustomCardView class.

As you can see we can construct a CustomButton instance using color and titleString.

    let blackButton = CustomButton(color: .black, titleString: "Black Button")
    let greenButton = CustomButton(color: .green, titleString: "Green Button")
    let purpleButton = CustomButton(color: .purple, titleString: "Purple Button")
    let orangeButton = CustomButton(color: .orange, titleString: "Orange Button")
    let yellowButton = CustomButton(color: .yellow, titleString: "Yellow Button")
    let brownButton = CustomButton(color: .brown, titleString: "Brown Button")
    let cyanButton = CustomButton(color: .cyan, titleString: "Cyan Button")
    let blueButton = CustomButton(color: .blue, titleString: "Blue Button")

Next we will create a StackView.

var stackView = UIStackView()

In a separate function we are constructing the stackView using arrangedSubviews constructor that takes in array of subviews.

   fileprivate func setupButtonsStackView() {
        stackView = UIStackView(arrangedSubviews: [blackButton, greenButton, purpleButton,orangeButton,yellowButton,brownButton,cyanButton,blueButton ])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.distribution = .fillEqually
        stackView.axis = .vertical
        stackView.spacing = 8
        addSubview(stackView)
        
        stackView.topAnchor.constraint(equalTo: topAnchor, constant: 24).isActive = true
        stackView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
        stackView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
        stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
    }

In this function we are constructing the StackView using the arrangedSubviews array that takes in array of UIViews, in this case out CustomButtons.
Then in order to enable auto layout, we are equalling translatesAutoresizingMaskIntoConstraints to false. We are setting the distribution to fillEqually so that all the buttons fill the stackView equally. The axis of the stack is vertical. We want the buttons to pile on top of each. You can use .horizontal that will put the buttons side by side. Spacing gives us some bit of gap between the UIViews.

Next, we are adding the StackView as a Subview to the CustomCard.

And this is what we get:

The gestures still work seamlessly. And you can see list of buttons equally populating the defined space on the Card view. We didn’t need to set up constraints on individual buttons and using subclasses, we were also able to make multiple buttons really quickly without having to write same line of codes each time.

I am just scratching the surface of UIStackView here. You can create beautiful and complex designs just by using StackViews. I hope you found this tutorial helpful.

Follow me on Twitter. I write articles on Swift and Algorithms on a weekly basis!

How to create Twitter card-like menu using Swift (Part 1)

Let’s create this Twitter card-like menu interface that you can invoke by tapping on the three dots on the top right hand cornerwhen you visit someone’s profile.  The initial setup is simple by creating a custom UIView which you can populate with buttons or a table view or whatever you want. I will go over that bit some day later. The main topic of discussion will be the gesture and dragging.

The code for this project is available on Github here.

 

 

 

Assuming you have created a UIView, we will set up it’s constraints like so:

bottomAnchorConstraint = card.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: cardHeight)
        bottomAnchorConstraint?.isActive                                    = true
        card.leftAnchor.constraint(equalTo: view.leftAnchor).isActive       = true
        card.rightAnchor.constraint(equalTo: view.rightAnchor).isActive     = true
        card.heightAnchor.constraint(equalToConstant: cardHeight) .isActive = true

bottomAnchorConstraint is a variable that I initialized on top and it’s of type NSLayoutConstraint?

 var bottomAnchorConstraint: NSLayoutConstraint?
 let cardHeight = UIScreen.main.bounds.height * 3/4
 var startingConstant : CGFloat = 0

Setting the card’s bottom anchor to this variable will allow us to change it’s value later on during animation. The cardHeight is 3/4 the size of the screen (something you can change) and startingConstant is initialized to zero. More on this later.

Okay now it’s time to animate. The first thing we will do is to set up UIPanGestureRecognizer on the card view.

card.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(sender:))))

To get the full power out of this, make sure you use handlePanGesture(sender: ) selector function.

In the selector function we can call sender.state and execute code depending upon the state at which the gesture currently is.

 switch sender.state {
            
        case .began:
            startingConstant = (bottomAnchorConstraint?.constant)!
        case .changed:
          let translationY = sender.translation(in: self.card).y
         self.bottomAnchorConstraint?.constant = startingConstant + translationY
            
         case .ended:
          if (Int((self.bottomAnchorConstraint?.constant)!)) < 0 { UIView.animate(withDuration: 0.2) { self.bottomAnchorConstraint?.constant = 0 } } else if sender.velocity(in: self.card).y > 0 {
                
                //Card is moving down
                if (sender.velocity(in: self.card).y < 300 && Int((self.bottomAnchorConstraint?.constant)!) < 180)
                {
                    card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
                        self.view.layoutIfNeeded()
                    }
                } else {
                    card.animateCardFlow(duration: 0.5, constraint: bottomAnchorConstraint!, constant: cardHeight, initialSpringVelocity: 0.6, usingSpringWithDamping: 0.9) { [unowned self] in
                        self.view.layoutIfNeeded()
                        self.testFlag.toggle()
                    }
                }
            }else {
                
                //Card is moving up
                card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
                    self.view.layoutIfNeeded()
                }
            }else {
                
                //Card is moving up
                card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
                    self.view.layoutIfNeeded()
                }
                
            }
        default:
            break
        }

This is the selector function. It’s a lot of code but it’s really simple. We are using the switch statement and checking three cases.

  • .began: In this case we are setting the startingConstant to bottomAnchorConstraint variable constant.
  • .changed: In this we are using the translation method to move the constraint as the user swipes.
  • .ended: In this case, we will be using another UIPanGestureRecognizer method of .velocity to check the direction of swipe.

Let’s discuss .changed in detail:

 case .changed:
          let translationY = sender.translation(in: self.card).y
         self.bottomAnchorConstraint?.constant = startingConstant + translationY

sender.translation.y is providing the change in points as the person drags on the view. Those points are added to the startingConstant which is assigned to the bottomAnchorConstraint.constant.

In .ended case:

I am using an extension to the UIView called “animateCardFlow” that does all the animation for me. Here is the snippet:

extension UIView {
    func animateCardFlow(duration: TimeInterval, constraint: NSLayoutConstraint, constant: CGFloat, initialSpringVelocity: CGFloat, usingSpringWithDamping: CGFloat, completion : @escaping () -> ()) {
        
        UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: usingSpringWithDamping, initialSpringVelocity: initialSpringVelocity, options: .curveEaseInOut, animations: {
            constraint.constant = constant
            completion()
        })
    }
}
          if (Int((self.bottomAnchorConstraint?.constant)!)) < 0 

                { UIView.animate(withDuration: 0.2) {
                 self.bottomAnchorConstraint?.constant = 0 } 
                } else if sender.velocity(in: self.card).y > 0 {
                
                //Card is moving down
                if (sender.velocity(in: self.card).y < 300 && Int((self.bottomAnchorConstraint?.constant)!) < 180)
                {
                    card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
                        self.view.layoutIfNeeded()
                    }
                } else {
                    card.animateCardFlow(duration: 0.5, constraint: bottomAnchorConstraint!, constant: cardHeight, initialSpringVelocity: 0.6, usingSpringWithDamping: 0.9) { [unowned self] in
                        self.view.layoutIfNeeded()
                        self.testFlag.toggle()
                    }
                }
            }else {
                
                //Card is moving up
                card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
                    self.view.layoutIfNeeded()
                }

If the bottomAnchorConstraint constant is above the center / original point when the gesture ends, bring the view back to zeroth location.

 if (Int((self.bottomAnchorConstraint?.constant)!)) < 0 

                { UIView.animate(withDuration: 0.2) {
                 self.bottomAnchorConstraint?.constant = 0 } 
                }

Else if the sender.velocity is greater than 0, that means the card is moving downwards. If the card is moving downwards but at velocity less than 300 points per second and the position of the card is still above 180 points, then the card show go back to the top position:

 if (sender.velocity(in: self.card).y < 300 && Int((self.bottomAnchorConstraint?.constant)!) < 180)
      {
         card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
          self.view.layoutIfNeeded()
     }

else if the speed is more than 300 or the card is already more than half way down then bring the card all the way down and dismiss it.

else {
        card.animateCardFlow(duration: 0.5, constraint: bottomAnchorConstraint!, constant: cardHeight, initialSpringVelocity: 0.6, usingSpringWithDamping: 0.9) { [unowned self] in
          self.view.layoutIfNeeded()
          self.testFlag.toggle()
     }

If the sender.velocity < 0 the card is moving up so move the card back to the top:

else {
                
                //Card is moving up
                card.animateCardFlow(duration: 0.3, constraint: bottomAnchorConstraint!, constant: 0, initialSpringVelocity: 3, usingSpringWithDamping: 0.9) { [unowned self] in
                    self.view.layoutIfNeeded()
                }

This is what you will get with the above code:

via GIPHY

The lag is due to the simulator. On real device, the animation is smooth. However there is a slight issue. If you continue dragging the card up you will see the background color (white color) appearing at the bottom of the card as seen here.

via GIPHY

We somehow need to stretch the card if we keep dragging it to the top despite it being in the “top” position. One way I decided to fix this issue was by attaching another UIView right underneath the card view. The bottomAnchor of the UIView was linked to the main view’s bottom anchor and the topAnchor was attached to the card view’s bottom anchor.

I am naming the view backBar: 

 backBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
 backBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
 backBar.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
 backBar.topAnchor.constraint(equalTo: card.bottomAnchor).isActive = true

With this even when you drag the card view upwards, we get the feeling of the card stretching.

via GIPHY

So here is how you can create the card interface with full dragging ability. I will keep working on this and you can expect part 2 of this tutorial next week where we make list of buttons and improve the details and stretching behavior even further.

Make sure to check out my other tutorials on iOS programming and algorithm solving!

Solving longest consecutive sequence in an array using Swift

This question is asked quite often in technical interview . The problem is this:

Write a function that takes in an unsorted array of integers and returns the longest range or longest consecutive sequence. For example:

let array = [3,2,4,5,6,7,12,10,8,9,21,15,11,13,16,19,20,19]

In this array there are 18 elements, however the longest consecutive goes from 2 to 13. You can simply eye ball the above array and see consecutive numbers from 2 all the way till 13. Then you have another range from 19 to 21 and another even smaller one that goes from 15 to 16.

The function should return the start and end values of the sequence.

I strongly urge to first try this problem out before checking out my version of this solution given below.

Alright, now let’s dive in to the solution. In my version of this solution, I am using only one Hash Map or Dictionary.The dictionary is of type [Int : Bool]. When we have used an integer successfully in creating a sequence the bool value should become true.

Here is the function signature:

func findLongestRange(in array: [Int]) -> String {

Here I am initializing the dictionary and three variables to keep track of the longest sequence and in the for loop, I am giving value of false to all the entries of the dictionary.

 var numbersDic = [Int: Bool]()
      
        
        var longestSequence = 0
        var recordInitialNumber = 0
        var recordLastNumber = 0
        
        if array.count <  1 {
            return "No Sequence Found"
        }
      
        // Adding the numbers as keys with Bool as the value in the dictionary
        for n in array {
            numbersDic[n] = false
        }
      
for n in array {
           
            if ( numbersDic[n]! ) {
                continue
            }
            var previousVal = n - 1
            var nextVal = n + 1
            
            numbersDic[n] = true
            
            while numbersDic[previousVal] != nil {
                numbersDic[previousVal] = true
                previousVal = previousVal - 1
            }
            
            
             //Checking if a next value is available
            while numbersDic[nextVal] != nil {
                numbersDic[nextVal] = true
                nextVal = nextVal + 1
            }

This is the meat of the code. In this part, we are essentially first checking if the number we have landed on is already used in some other sequence by checking its value. If its true, we continue with the next iteration of the loop otherwise we continue. Then we are are setting previousVal and nextValue as current value (n) – 1 and current value + 1 respectively.
By using two while loops, we are first looking for the previous value to the current number in the hash map. If the value for it exists or the key is available, the bool value is switched to true. If there is no value in the dictionary for the previousVal key the while loop ends and goes to the next loop where we check the next value in the loop. Again the same thing, adding 1 to the nextVal and checking its presence in the hash map.

Then at the end we are checking if the current two values of nextValue and previousValue has the highest amount of numbers between them by taking the difference and returning a string:

//Checking longest sequence
            
            let difference =  (nextVal - 1) - (previousVal + 1)
           
            if (difference > longestSequence) {
                longestSequence = difference
                recordInitialNumber = previousVal + 1
                recordLastNumber = nextVal - 1
                
            } 
          return "The longest sequence starts at \(recordInitialNumber) and ends at  \(recordLastNumber)"

This is the output you get when you run this code:

The Longest sequence starts at 2 and ends at 13

Checking this algorithm in leetcode returns this result:

 

Solving 3SUM problem using Swift

The 3Sum problem is often asked during technical interviews and the problem at first seems quite tough but it’s got a pretty similar taste to 2Sum problem.

Here is the problem: You are given a sorted array, and you need to find three numbers in the array that can add up to a particular sum which is also given to you.

I would strongly urge everyone to first try solving the 2Sum problem where it requires you to find 2 numbers that add up to give you the given sum and similarly practice this problem before continuing to the solution given below. 

So first lets start with function signature:

func threeSumProblem(for array: [Int], sum: Int) -> Bool

The function takes in the sorted array of type Int and a Sum integer and returns a Bool.

func threeSumProblem(for array: [Int], sum: Int) -> Bool
    {
        var complement = 0
        var lowIndex = 0
        var highIndex = array.count - 1

Firstly, I am initializing three variables – complement, lowIndex & highIndex. To solve the 3Sum problem here is the method I am using:  If the array is say [1,4,6,4] and sum is 11, then we will first hold on to first number which in this case is 1, and then using linear pointer method on the remaining array [4,6,4]. Here we will have a pointer which will start from 4 with one index more than the number we holding on to, and the high pointer will start with index of array.count – 1. (The right hand side)

for (index, element) in array.enumerated() {
            lowIndex = index + 1
            complement = sum - element
            while (lowIndex < highIndex) 
                { if (array[lowIndex] + array[highIndex] > complement ) {
                    highIndex -= 1
                }else if (array[lowIndex] + array[highIndex] < complement) {
                    lowIndex += 1
                }else {
                    print(element, array[lowIndex], array[highIndex])
                    return true
                }
            }
            
             lowIndex = 0
             highIndex = array.count - 1
           
        }

Next, we will start a for loop which will give us the element and the index of that element in the sequence, hence using array.enumerated() . Then inside the for loop, I am setting lowIndex to current index + 1. We also set complement by subtracting the current element we are on with the sum passed in the function by the user.

Then we have another loop inside this for loop. This time it’s a while loop. The while loop will keep running until the two pointers cross over.

Inside the while loop, we have three if statements:

 if (array[lowIndex] + array[highIndex] > complement ) {
                    highIndex -= 1
                }else if (array[lowIndex] + array[highIndex] < complement) {
                    lowIndex += 1
                }else {
                    print(element, array[lowIndex], array[highIndex])
                    return true
                }

Now the problem has been converted in to 2sum. We will check if the two numbers sum is greater than complement, if yes, then move the highIndex pointer by 1 to the left. Similarly, if the sum of two numbers is less than the complement, then move the lowIndex pointer by 1 to the right. Else, if the sum of two numbers is equal to complement, then return true.

After this if statement, we are setting the lowIndex and highIndex back to 0 and array.count – 1.

Next call this function:

let array = [4,3,5,1,5]
print(threeSumProblem(for: array, sum: 14))

The output for this is:

//output
4 5 5
true

This is how you can solve this algorithm. This particular solution is O(n^2) mainly as we are using two nested loops.