Animating UIView by changing constraints

That’s one of the most fun and easiest animations you can do.

First, let’s make sure we know what constraint to change: you’ll have to make sure you either have the constraint as an @IBOutlet or -if you’ve added in code- make sure you add an identifier to it. 

Then you’ll trigger a constant change within an animation block, in this first example we’re using the identifier to find the right constraint:

        UIView.animate(withDuration: 0.2) {

            let identifyTheRightConstraint = self.parent!.view.constraints.filter{ $0.identifier == “distanceOfMessageToTop” }

            if let constraint = identifyTheRightConstraint.first{

                constraint.constant = 0

            }

            self.parent!.view.layoutIfNeeded()

 

        }

 

Or this is you’re using an @IBOutlet

        UIView.animate(withDuration: 0.2) {

 

            IBOutletConstraint.constant = 0

 

            self.parent!.view.layoutIfNeeded()

 

        }

 

It’s very important that you’re triggering layoutIfNeeded() for the parent view of the view you’re changing the constraints on. And that’s it!  

Questions / comments / suggestions? @MarcMasVi 

Marc

Opening links in Safari / Embedded Safari – iOS

In iOS you can either do it with a push:

let safariVC = SFSafariViewController(url: URL(string: “www.google.com”)!, configuration: .init())

 

present(safariVC, animated: true, completion: nil)

 

or directly in the Safari app:

UIApplication.shared.open(URL(string:www.google.com”)!, options: [:]) { (_) in }

 

Very practical, 

 

Marc

Reloading/inserting Dynamic Height cells and keeping Scroll position

For an upcoming app I had to insert Dynamic Height cells while keeping the tableView offset -scrolling position- constant. This would typically be a trivial issue, but given that when using Dynamic Height cells nor the contentSize nor the offset can be trusted it’s a bit more tricky than it seems…

What I was trying to do was adding cells at the top of my tableView without any scroll taking place, similarly to what Twitter, Tweetbot or Twitterrific apps do. 

Screen Shot 2018 12 30 at 6 17 23 PM

Green cells in the “After” scenario show how the new cells have been inserted at the top, without affecting scrolling. 

The approach that ended up working smoothly was:

Step 1. Storing in a variable the UITableViewCell below the Navigation Bar and storing the offset of that cell in relation to the Navigation Bar. 

Step 2. Insert cells / reloadData.

Step 3.Scroll to the cell we saved before the insert/reload and then add the offset. 

Here’s the code:

 

        UIView.performWithoutAnimation {

            //Step 1 Detect & Store

            let topWillBeAt = getTopVisibleRow() + countOfAddedItems

            let oldHeightDifferenceBetweenTopRowAndNavBar = heightDifferenceBetweenTopRowAndNavBar()

            

            //Step 2 Insert

            self.tableView.insertRows(at: arrayOfIndexPaths, with: .none)

            

            //Step 3 Restore Scrolling

            tableView.scrollToRow(at: IndexPath(row: topWillBeAt, section: 0), at: .top, animated: false)

            tableView.contentOffset.y = tableView.contentOffset.y – oldHeightDifferenceBetweenTopRowAndNavBar

 

        }

 

And supporting functions:

    func getTopVisibleRow () -> Int {

        //We need this to accounts for the translucency below the nav bar

        let navBar = navigationController?.navigationBar

        let whereIsNavBarInTableView = tableView.convert(navBar!.bounds, from: navBar)

        let pointWhereNavBarEnds = CGPoint(x: 0, y: whereIsNavBarInTableView.origin.y + whereIsNavBarInTableView.size.height + 1)

        let accurateIndexPath = tableView.indexPathForRow(at: pointWhereNavBarEnds)

        return accurateIndexPath?.row ?? 0

    }

    

    func heightDifferenceBetweenTopRowAndNavBar()-> CGFloat{

        let rectForTopRow = tableView.rectForRow(at:IndexPath(row:  getTopVisibleRow(), section: 0))

        let navBar = navigationController?.navigationBar

        let whereIsNavBarInTableView = tableView.convert(navBar!.bounds, from: navBar)

        let pointWhereNavBarEnds = CGPoint(x: 0, y: whereIsNavBarInTableView.origin.y + whereIsNavBarInTableView.size.height)

        let differenceBetweenTopRowAndNavBar = rectForTopRow.origin.y – pointWhereNavBarEnds.y

        return differenceBetweenTopRowAndNavBar

 

    }

 

Questions / comments / suggestions? @MarcMasVi 

PS. On another topic, if you get some jumpy scrolling with Dynamic Height cells I strongly suggest you to look into this question from Stack Overflow. 

Marc

Measuring function execution time

This is a very quick one. For an app I’m working on I wanted to measure how long it took a given function to complete. 

I typically don’t do this, as most functions are very fast, but this one involved a server fetch and some background processing. Anyhow, I’m getting distracted, here it is -easy as pie-:

 

let startTime = Date()

 

//Code

 

let endTime = Date()

let timeElapsed: Double = endTime.timeIntervalSince(startTime)

 

print(“Time taken \(timeElapsed) seconds”)

 

Questions / comments / suggestions? I’m at @MarcMasVi 

Marc

 

 

Dealing with UIGraphics ImageContext ‘Memory Leaks’

Here’s the code to change the image size of an image in iOS so that then it can be saved with smaller footprint:

let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)

        

// Actually do the resizing to the rect using the ImageContext stuff

UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)

image.draw(in: rect)

        

let newImage = UIGraphicsGetImageFromCurrentImageContext()

 

UIGraphicsEndImageContext()

 

This works great if you do a few size conversions, however if you do hundreds or thousands you’ll see that “UIGraphicsGetImageFromCurrentImageContext” will hold onto the image in memory until the code returns control to the runloop, which may not happen for a long time. This may lead to crashes when iOS decides your app is using too much memory. Here’s what was happening to my app:

Screen Shot 2018 10 06 at 11 10 25 AM

To solve this I’m using a new autorelease pool at every cycle forcing it to release the memory every time. Works great, here’s the implementation of the autorelease pool code:

private func resizeImage(image: UIImage, toHeight: CGFloat) -> UIImage {

        return autoreleasepool { () -> UIImage in

             […]

             return newImage!

        }

 

    }

And the implementation of the full function for reference:

    private func resizeImage(image: UIImage, toHeight: CGFloat) -> UIImage {

        return autoreleasepool { () -> UIImage in

            let toWidth = image.size.width * (toHeight/image.size.height)

            let targetSize = CGSize(width: toWidth, height: toHeight)

            let size = image.size

        

            let widthRatio  = targetSize.width  / size.width

            let heightRatio = targetSize.height / size.height

        

            // Orientation detection

            var newSize: CGSize

            if(widthRatio > heightRatio) {

            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)

            } else {

            newSize = CGSize(width: size.width * widthRatio,  height: size.height * widthRatio)

            }

        

            // Calculated rect

            let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)

        

            // Resize

            UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)

            image.draw(in: rect)

        

            let newImage = UIGraphicsGetImageFromCurrentImageContext()

            UIGraphicsEndImageContext()

        

            return newImage!

        }

        

 

    }

 

Comments / questions? I’m at @MarcMasVi 

 

Marc

Pre-Scrolling a Table to a specific cell at load

Although that’s something that appears fairly easy, the truth -for me at least-, is that scrolling a UITableView to a specific cell every time you load it may prove tricky. 

In the app I’m working on I implement it in the following way: every time a view appeared I assess if the top visible row is the one it should. If it is, no action is taken, else we’ll scroll to the right row. 

    override func viewDidAppear(_ animated: Bool) {

        super.viewDidAppear(animated)

        //Check if we need to scroll to a specific cell

        if getTopVisibleRow() != getTableLastViewedTopPosition() {

            tableView.scrollToRow(at: IndexPath(row: getTableLastViewedTopPosition(), section: 0), at: .top, animated: false)

        }

 

    }

For reference here’s what I’m doing in the getTopVisibleRow(), note that is is important only if you’re using a translucid navbar:

    func getTopVisibleRow () -> Int {

        //We need this to accounts for the translucency below the nav bar

        let navBar = navigationController?.navigationBar

        let whereIsNavBarInTableView = tableView.convert(navBar!.bounds, from: navBar)

        let pointWhereNavBarEnds = CGPoint(x: 0, y: whereIsNavBarInTableView.origin.y + whereIsNavBarInTableView.size.height + 1)

        let accurateIndexPath = tableView.indexPathForRow(at: pointWhereNavBarEnds)

        return accurateIndexPath?.row ?? 0

 

    }

For getTableLastViewedTopPosition() I’m simply fetching from the model what cell index should we be showing. 

Questions / comments? I’m at @MarcMasVi 

Marc

Find the right value in an Array, or in an Array of Dictionaries

From time to time I discover a new way of coding something that significantly improves readability & minimizes how much I need to code. 

Here’s one of such latest discoveries. Typically to find a value in an array (or in an array of dictionaries) with a specific condition I would do a for loop with an if statement checking for the right value. This works great of course, but its a lot of code to do something that simple. 

Here’s a simple way to find a value in an array of dictionaries:

let matchingArticle = arrayOfItems.first(where: {$0.exampleKey == 3843})

 

We ask the arrayOfItems to give us the first value where the dictionary key exampleKey takes the value 3843. 

And if it’s only an array of int or string, for example, is even easier:

 let matchingArticle = arrayOfItems.first(where: {$0 == 859})

We simply ask the arrayOfItems to give us the first value where the its value equals 859. 

Note, in this code example we assume you will always have only up to 1 match. 

I’ve come to love this function and I think you will too. 

 

Questions / comments / suggestions? I’m at @MarcMasVi 

Marc

 

Inserting cells at the top of a UITableView with no scrolling

If you would like your table to be refreshed “ala Twitter”, with new data being added on top, here’s a quick an easy way to do it for iOS: 

func updateTableViewAfterAdditionInNewArray(){

        //Get new feeds and check if counts are different

        let countOfAddedItems = oldArray.count – newArray.count

        

        //If we’re adding information

        if (oldArray) > 0 {

            if debugMode == true{

                print(“We have a difference in counts of \(oldArray)”)

            }

            

            if oldArray.count != 0 {

                //Update data model

                oldArray = newArray

                

                var initialContentOffSet = tableView.contentOffset.y

                //If offset is less than 0 due to refresh up gesture, assume 0

                if initialContentOffSet < 0 {

                    initialContentOffSet = 0

                }

                //Reload, scroll and offset

                tableView.reloadData()

                tableView.scrollToRow(at: IndexPath(row: countOfAddedItems, section: 0), at: .top, animated: false)

                tableView.contentOffset.y = tableView.contentOffset.y  + initialContentOffSet

            }else{

                if debugMode == true{

                    print(“There was no data in oldArray, avoid scrolling. “)

                }

                //Update data model

                oldArray = newArray

                

                tableView.reloadData()

            }

            

        }

 

    }

Hope it helps! I’m really enjoying going back to iOS development, expect more iOS posts this next couple of months as I’m ramping up on my next project. 

Questions / comments / suggestions? I’m at @MarcMasVi 

 

Marc

Improve your ⌘ V efficiency

Want to improve your quality of life in 5 seconds?

1. Go to System Preferences

2. Keyboard

3. Shortcuts -> App Shortcuts

4. Click on add -> All Applications –> add “Paste and Match Style” for keyboard shortcut ⌘V

Thank you Ally

Marc

Multi Thread access to shared Variables – Swift 4 Concurrency

Working with background threads really helps improve the app responsiveness & the overall user experience. But, and it’s a big but, you have to be aware of multithread access conflicts. 

Screen Shot 2018 06 17 at 11 01 43 AM

If you access/modify the same variable from multiple threads you’re prone to non-reproducible -seemingly random- crashes.

How to Detect Conflicts?

To help you find where you may have this variable access conflicts you should enable Thread Sanitizer & Pause on issues in your Run Scheme settings. Xcode will highlight conflicts while the app runs, as it detects them. If you want to dig deeper in what the debugger can do for you there’s a great WWDC 2018 session worth watching

Screen Shot 2018 06 17 at 10 29 38 AM

Once we have detected the variables that are being accessed from different threads it’s time to strengthen them.

How to Address Multi-Thread Variable Access Conflicts?

We must protect the access to the variables so that no simultaneous read/write action can occur. The easiest way is to use accessors for the variable, accessors can be accessed from any thread but the value of the variable always will be fetched from one thread (that we create for this purpose). Here’s an example to illustrate:

    //Background queue to synchronize data access

    fileprivate let globalBackgroundSyncronizeDataQueue = DispatchQueue(

        label: globalBackgroundSyncronizeSharedData”)

 

    //Value variable (always accessed from created thread) 

    var arrayOfFeedItems_Value : [String] =  []

 

    //Variable accessor that can be accessed from anywhere (multithread-protected).

    var arrayOfFeedItems : [String] {

        set(newValue){

            globalBackgroundSyncronizeDataQueue.sync(){

                self.arrayOfFeedItems_Value = newValue

            }

        }

        get{

            return globalBackgroundSyncronizeDataQueue.sync{

                arrayOfFeedItems_Value

            }

        }

 

    }

In this case I’m adding multi-thread protection to an array (arrayOfFeedItems), but you could do the same with any variable. 

This has been a fun learning experience for me, hope this post helps other with the same challenges.

Questions / comments? I’m at @MarcMasVi 

Marc

 

Some extra documentation if you want to dig deeper: Swift Access Races & Framework Dispatch.