openpics

OpenPics is an open source iOS application for viewing images from multiple remote sources. I am finally starting the process of rewriting it in Swift. During the process you can follow along at my new repo, mock me in the pull requests, send encouraging issues, etc.

This will be a complete rewrite, focusing on making the application simpler and easier to update as changes happen to iOS. Currently, the biggest issue I have is all the special case code I use regarding the UICollectionView classes, specifically layout to layout transitions. There is already a radar regarding auto layout constraints and layout to layout transitions. If I want to support multitasking on iPad, I need size classes, if I need size classes, I need auto layout. Perhaps it isn’t quite that simple, but to hear Apple talk, it should just work! Unfortunately, not much happening on that Radar since WWDC 2014…so I have decided to move on.

openpics1

For now, I am going to use two separate UICollectionViewControllers, one for image thumbnails and one for full size images. (Perhaps I will eventually add a custom transition). This isn’t ideal, but it will have to do for now. The Swift code for the thumbnail is simple. I set the item size based on the width of the frame. However, this has the unfortunate side effect of not adjusting on rotation, so I’ll need to handle that later.

1
2
3
4
5
6
7
8
9
override func viewDidLoad() {
    super.viewDidLoad()

    self.collectionView!.dataSource = dataSource
    let layout = self.collectionViewLayout as! UICollectionViewFlowLayout
    
    let itemSize = (self.view.frame.width - 6) / 4
    layout.itemSize = CGSizeMake(itemSize, itemSize)
}

Also, you will notice that I abstracted the UICollectionViewDataSource. Currently the project is very simple, but separating out your data sources is the easiest way to avoid view controller bloat. The earlier the better. The Lighter View Controllers article at Objc.io is a great tutorial on abstracting data sources.

However, currently, my abstracted data source is mostly just placeholder code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ImageDataSource: NSObject, UICollectionViewDataSource {
    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 8
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! ImageCollectionViewCell
        
        // Configure the cell
        cell.imageView.contentMode = .ScaleAspectFit
        cell.imageView.image = UIImage(named: "photo\(indexPath.item)")
        
        return cell
    }
}

More interesting things are happening in the full screen collection view controller. I found a good tutorial on UICollectionViewController and photo viewers. However, it is a few years old now and I think some of the rotation code is a bit out of date.

Further I was really struggling with the item size for this controller. As you can see in this code, I had to subtract off the content inset to avoid an error from Xcode. Not sure what that is about.

1
2
3
4
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    let layout = self.collectionView!.collectionViewLayout as! UICollectionViewFlowLayout
    return CGSizeMake(self.collectionView!.frame.size.width, self.collectionView!.frame.size.height - self.collectionView!.contentInset.top) 
}

Finally, as you can see in the animation above, it jumps to the selected index path after becoming visible.

1
2
3
override func viewDidAppear(animated: Bool) {
    self.collectionView!.scrollToItemAtIndexPath(self.currentIndexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: false)
}

Obviously, this is not ideal.

But these are issues for another day. Check out the repo, and stay tuned for Part 2!