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.

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

 

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:

 

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.

Creating a NSItemProvider for custom model class (Drag & Drop API)

For a project that I will start working on this summers, I am thinking about implementing this feature where you can drag and drop one or multiple items on a view (circleView in this tutorial) and download all those items from the web. The idea of using drag and drop is nothing new in iOS and with iOS 11, Apple has introduced a specific API for users to do just this.

This API allows users to drag items within the app or from one app to another. On iPhones you can only drag the items within the app  while on iPad with the help of split view you can drag items from one app to another.

The drag and drop API is extremely easy to use and configuring it takes just few minutes. There are two separate protocols that deal with drag and drop. Also there is a UITableViewDragDelegate and UITableViewDropDelegate for dragging and dropping items between table views or within same table view.

NSItemProvider is a wrapper that creates a UIDragItem. Essentially, if you want to drag and drop items of type Strings, then you first have to wrap it with an itemProvider that will convert the data to NSString and create a UIDragItem. Then when you drop the item, it will convert it from NSString to String. Apple provides ItemProvider for types like UIImage, NSURL, NSString etc

If you are populating a table view with items of custom class then in order to drag and drop these items you have to use a custom NSItemProvider. In order to do this, you need to conform your model class to NSItemProviderReading, NSItemProviderWriting.

final class PodcastModelData: NSObject, Codable, NSItemProviderReading, NSItemProviderWriting {

Make sure to use NSObject and Codable. Codable protocol is a combination of two other protocols – Encodable & Decodable. We will use Codable to convert our object to JSON data and then when we drop it at the destination, it will revert back to the model class object.

final class PodcastModelData: NSObject, Codable, NSItemProviderReading, NSItemProviderWriting {
   
    
  // 1  
    var collectionName : String?
    var feedUrl: String?
    var artworkUrl100: String?
  // 2  
     init(collectionName: String, feedURL: String, artworkURL100: String) {
        self.collectionName = collectionName
        self.feedUrl = feedURL
        self.artworkUrl100 = artworkURL100
    }
   //3 
    static var writableTypeIdentifiersForItemProvider: [String] {
        return [(kUTTypeData) as String]
    }
    
// 4
    func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
        
        
        let progress = Progress(totalUnitCount: 100)
        // 5
        
        do {
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted
            let data = try encoder.encode(self)
            let json = String(data: data, encoding: String.Encoding.utf8)
            progress.completedUnitCount = 100
            completionHandler(data, nil)
        } catch {
     
            completionHandler(nil, error)
        }
        
        return progress
    }

Let’s break down the above code:

  1. Class properties – three variables all of type of strings
  2.  simple initializer.
  3. First method to conform to –  ‘writableTypeIdentifiersForItemProvider‘ method returns an array of type of identifiers as Strings. Again it’s array so you can give them multiple identifiers but make sure that it’s in the order of precedence. We will be using KUTTypeData since we want to send our item as type Data.
  4. The second method to conform to –  in this method you will convert the  object to the type identifier which in our case is KUTTypeData. So we will be converting the object to JSON.
  5. We are simply encoding the class properties to JSON using JSONEncoder(). The progress variable keeps track of the loading / conversion.
  6. As soon as the loading is complete, the completion hander closure is called and the converted data is passed.

Now lets conform to NSItemProviderReading:

// 1
static var readableTypeIdentifiersForItemProvider: [String] {
        return [(kUTTypeData) as String]
    }
// 2
    static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> PodcastModelData {
        let decoder = JSONDecoder()
        do {
            let myJSON = try decoder.decode(podcastModelData.self, from: data)
            return myJSON
        } catch {
            fatalError("Err")
        }
        
    }

Lets break down the above code:

  1. readableTypeIdentiferForItemProvider – again returning array of type identifiers in order of highest fidelity. In this case we are only returning KUTTypeData.
  2. This method creates a new instance of class with the given data and the identifier. In this method we will be using JSONDecoder() to decode the data back to instance of class (podcastModelData). The actual function returns ‘self’ however, it was giving me some issues so I added final infront of the class name and instead of ‘self‘ wrote the class name.

That’s it really! Now you it’s pretty straight forward to create UIDragItems with custom NSItemProvider.

func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
     
        let podcastItem = PodcastModelData(collectionName: collectionName, feedURL: feedUrl, artworkURL100: artworkUrl)
        
        let itemProvider = NSItemProvider(object: podcastItem)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        return [dragItem]
    }

The NSitemProvider constructor requires the object of the class and not the class itself. In this case I am giving it podcastItem. The drag item constructor requires the itemProvider which we created earlier.

Since I am dropping all these elements on a custom circleView, therefore I am using UIDropInteractionDelegate and using this method:

   func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
     session.loadObjects(ofClass: PodcastModelData.self) { (items) in
            if let podcasts = items as? [PodcastModelData] {
                //....
              //Do whatever you want to do with your dropped items here
            }
            
        }
      
    }

If you want to drop it on a table view then you have to use this function:

func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { 
...
}

 

You can get the full source code for this project on my github here. Also check out the app in action here on my Twitter.

Interview Question: How to solve the recurrent value problem?

So I was reading an article where the person was talking about the internship interview process and in it, the person was asked 2 algorithm related questions. I decided to solve them myself and see how I would approach such a problem.

Here is the problem — you are given an array of integers [1,2,1,3,3] and you want to find:

    1. The first consecutive recurring number. Then answer of this will be 3.
    2. The first recurring number in the array. The answer of this will be 1.

Continue reading “Interview Question: How to solve the recurrent value problem?”