-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 25 KB
/
content.json
1
{"meta":{"title":"Villy G's blog","subtitle":"Code.Better()","description":"Thoughts on software development craftsmanship.","author":"Villy G","url":"http://villyg.com"},"pages":[{"title":"About","date":"2017-02-05T01:11:49.000Z","updated":"2017-03-18T12:30:52.000Z","comments":false,"path":"about/index.html","permalink":"http://villyg.com/about/index.html","excerpt":"","text":"Hi, I’m Villy G and I love software craftsmanship. Background I am a Software Engineering Manager with UPMC Enterprises here in Pittsburgh, PA. I’ve worked with just about any major stack used in the BigCorp environment. If my memory serves me well - my first app in production was written in Microsoft.NET 1.0 way back in 2003 and it has been many lines of code since then. In the last few years however I find myself drawn to JavaScript (front-end and server-side) as well as iOS and Android development so my blog will focus primarily on those."},{"title":"Projects","date":"2017-02-07T00:42:19.000Z","updated":"2017-03-18T12:30:52.000Z","comments":false,"path":"projects/index.html","permalink":"http://villyg.com/projects/index.html","excerpt":"","text":"Gun VaultGun Vault is an IOS app designed to be a personal record keeping tool for anyone who owns a firearm collection. It will help keep track of various details about each item - make, model, serial number, caliber, finish etc. Photo Slide Show - SwiftThe Photo Slide Show project is a Swift port of the Apple’s PhotoScroll app demoed during the 2012 WWDC event. It doesn’t include the Tile logic but it has a fully functional slide-show functionality using UIPageViewController and UIScrollView."},{"title":"Gun Vault","date":"2017-02-28T22:55:40.000Z","updated":"2017-03-19T15:55:03.000Z","comments":true,"path":"projects/gunvault/index.html","permalink":"http://villyg.com/projects/gunvault/index.html","excerpt":"","text":"DescriptionGun Vault is designed to be a personal record keeping app for anyone who owns a firearm collection. Features Firearms inventory - keep track of various details about each item: make, model, serial number, caliber, finish etc. Photo library - capture/import, organize and share photos for each firearm. Customizations - customize various lookup options in order to optimize entering data. Supported devicesGun Vault supports all iPhone and iPad devices running iOS 10 or newer. PriceGun Vault is completely free and is available on the App Store. Version changes1.3 Added ability to record purchase date and price Minor bug fixes and improvements 1.2 Added ability to list guns grouped by make, type or caliber Added ability to delete already entered lookup values for types, makes, calibers, actions and finishes Added photo carousel with rotation support on gun details screen Added app version number on splash screen Simplified data entry UI Minor bug fixes and improvements 1.1 Added ability to capture or import photos for each gun Added ability to manage gun photos Added ability to view gun photos as slide show Added ability to share gun photos Minor performance and UI optimiziations 1.0 Added ability to record various details on guns: type, make, model, caliber, capacity, action, finish, serial number Added ability to configure gun entry settings: type, make, caliber, capacity, action, finish SupportHaving questions or feedback? I’d love to hear from you. function ContactForm() { var contactform = document.getElementById('contactform'); contactform.setAttribute('action', '//formspree.io/' + 'villyg' + '@' + 'icloud' + '.' + 'com'); } Name Email Message ContactForm()"}],"posts":[{"title":"Autosizing UICollectionView carousel during rotation","slug":"Autosizing-PhotoCarousel-with-UICollectionView","date":"2017-03-18T12:57:44.000Z","updated":"2017-03-19T15:56:16.000Z","comments":true,"path":"2017/03/18/Autosizing-PhotoCarousel-with-UICollectionView/","link":"","permalink":"http://villyg.com/2017/03/18/Autosizing-PhotoCarousel-with-UICollectionView/","excerpt":"In my previous post I demonstrated how to use AutoLayout and make UIImageView resize to full screen when in landscape mode. While happy with the outcome - the solution was not exactly what I wanted in the 1.2 version of Gun Vault. I wanted to allow to user to view all images from the item details screen using a scrollable carousel-style UICollectionView. I still wanted to have the carousel resize to full screen when in landscape mode as well prepare for future iPad oriented optimizations. It turned out that making a UICollectionView behave the same way as a UIImageView is much more difficult so I hope this article saves you time if you are trying to accomplish something similar so let’s get going.","text":"In my previous post I demonstrated how to use AutoLayout and make UIImageView resize to full screen when in landscape mode. While happy with the outcome - the solution was not exactly what I wanted in the 1.2 version of Gun Vault. I wanted to allow to user to view all images from the item details screen using a scrollable carousel-style UICollectionView. I still wanted to have the carousel resize to full screen when in landscape mode as well prepare for future iPad oriented optimizations. It turned out that making a UICollectionView behave the same way as a UIImageView is much more difficult so I hope this article saves you time if you are trying to accomplish something similar so let’s get going. Rotation supportOur view controller will contains a UICollectionView and few sample images MainController.swift123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172import UIKitclass MainController: UIViewController { var images: [UIImage] = { let image1: UIImage = UIImage(named: \"image1\")! let image2: UIImage = UIImage(named: \"image2\")! let image3: UIImage = UIImage(named: \"image3\")! let image4: UIImage = UIImage(named: \"image4\")! var result: [UIImage] = [] result.append(image1) result.append(image2) result.append(image3) result.append(image4) return result }() var collectionView: UICollectionView = { let frame = CGRect(x: 0, y: 0, width: 0, height: 0) let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal let result = UICollectionView(frame: frame, collectionViewLayout: layout) result.backgroundColor = UIColor.darkGray result.isPagingEnabled = true result.translatesAutoresizingMaskIntoConstraints = false return result }() var commonConstraints: [NSLayoutConstraint] = [] var landscapeConstraints: [NSLayoutConstraint] = [] var portraitConstraints: [NSLayoutConstraint] = [] override func viewDidLoad() { super.viewDidLoad() self.collectionView.register(ImageCell.self, forCellWithReuseIdentifier: ImageCell.reuseIdentifer) self.collectionView.delegate = self self.collectionView.dataSource = self self.automaticallyAdjustsScrollViewInsets = false self.view.addSubview(collectionView) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) // same as having the code block in viewDidLoad if commonConstraints.isEmpty { var collectionViewHeightMultiplierWhenInPortrait: CGFloat = 0.33 if self.traitCollection.userInterfaceIdiom == .pad { collectionViewHeightMultiplierWhenInPortrait = 0.5 } // define collection view common, portrait and landscape constraints commonConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0)) commonConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0)) commonConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0)) portraitConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: collectionViewHeightMultiplierWhenInPortrait, constant: 0)) landscapeConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: 1.0, constant: 0)) // activate common constriants NSLayoutConstraint.activate(commonConstraints) } if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass { NSLayoutConstraint.deactivate(landscapeConstraints) NSLayoutConstraint.deactivate(portraitConstraints) if traitCollection.verticalSizeClass == .regular { self.navigationController?.setNavigationBarHidden(false, animated: true) NSLayoutConstraint.activate(portraitConstraints) } else { self.navigationController?.setNavigationBarHidden(true, animated: true) NSLayoutConstraint.activate(landscapeConstraints) } self.collectionView.collectionViewLayout.invalidateLayout() } } }extension MainController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.images.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCell.reuseIdentifer, for: indexPath) as! ImageCell let image: UIImage = images[indexPath.row] cell.imageView.image = image return cell }}extension MainController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return collectionView.frame.size } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 0.0 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 0.0 } } Let’s go over the code. In viewDidLoad() we simply build our UI - we add a UICollectionView as a subview to the MainController’s view and turn on paging. Line 53 is important because othwerise we’ll get a bunch of errors around insets. In traitCollectionDidChange() we do most of the heavy lifting around AutoLayout. While the instructions look familiar, you might be used to seeing them in viewDidLoad() instead. The traitCollectionDidChange() method is actually equally if not better suited for setting up all AutoLayout stuff with the added benefit of keeping your viewDidLoad() slimmer. Similar to viewDidLoad() it also gets called on initial load but unlike viewDidLoad() it gets called again when there are changes to the UI - in example when the device is rotated or when on iPad a view is resized. As usual - there are some gotchas when it doesn’t happen but it is perfect for our use case. We’ll use it to set up some common constraints that will remain the same no matter what the orientation is as well as use it as a trigger to activate/deactivate portraint or landscape constraints only - show/hide the navbar, resize the collection view to full screen and back and so on. The only other thing we need is a UICollectionViewCell with a UIImageView inside. ImageCell.swift12345678910111213141516171819202122232425262728293031323334import UIKitclass ImageCell: UICollectionViewCell { static let reuseIdentifer: String = String(describing: type(of: self)) var imageView: UIImageView! override init(frame: CGRect) { super.init(frame: frame) imageView = UIImageView() contentView.addSubview(imageView) backgroundColor = UIColor.yellow imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true imageView.translatesAutoresizingMaskIntoConstraints = false imageView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0).isActive = true imageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1.0).isActive = true imageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: 0.0).isActive = true imageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0).isActive = true } required init?(coder aDecoder: NSCoder) { fatalError(\"init(coder:) has not been implemented\") } } If we run the example and rotate the device everything will look as expected as long as we stay on the first image. As soon as we go to the next and rotate again we’ll uncover our first bug. Fixing up offsetThe reason for our bug is very simple - we turned on paging but we forgot to instruct our collection view how to handle changes in page size so let’s go ahead and do that. We’ll add the following method to our MainController MainController.swift1234567891011121314151617181920212223242526override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) print(\"before\") let visiblePage = self.collectionView.contentOffset.x / self.collectionView.bounds.size.width coordinator.animate(alongsideTransition: { (context) in print(\"during\") let newOffset = CGPoint(x: visiblePage * self.collectionView.bounds.size.width, y: self.collectionView.contentOffset.y) self.collectionView.contentOffset = newOffset self.collectionView.collectionViewLayout.invalidateLayout() }) { (context) in print(\"after\") } } The code in the viewWillTransitionToSize() method will figure out the current page and will instruct the transition coordinator to adjust the collection view’s offset to the appropriate X coordinate as part of the animation during rotation. This method is relatively new so I added few print statements to show you what gets called when. If you run the app now you will see that our bug is fixed. Smoothing things outWhile it is mission accomplished already as far as the main use case goes, those of us that are a bit more detail oriented have probably already noticed the weird artifacts happening during rotation. If you are not one of them - run the app again and pay closer attention - do you see the flickering now? I bet you do. Let’s go ahead and address it. Rather than tinckering with the UICollectionView’s animation we’ll use a very simple trick using a hidden UIImageView with the same constraints as the UICollectionView. Before rotation: take a snapshot of the currently displayed image and load it in the image view; hide the collection view and show the image view. During rotation: rotate both the image view and the collection view After rotation: hide the image view and show the collection view Let’s add our image view to the controller as well as update the AutoLayout instructions. MainController.swift123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990var tempImageView: UIImageView = { let result = UIImageView() result.contentMode = .scaleAspectFill result.clipsToBounds = true result.translatesAutoresizingMaskIntoConstraints = false result.isHidden = true return result }()override func viewDidLoad() { super.viewDidLoad() self.collectionView.register(ImageCell.self, forCellWithReuseIdentifier: ImageCell.reuseIdentifer) self.collectionView.delegate = self self.collectionView.dataSource = self self.automaticallyAdjustsScrollViewInsets = false self.view.addSubview(collectionView) self.view.addSubview(tempImageView) }override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) // same as having the code block in viewDidLoad if commonConstraints.isEmpty { var collectionViewHeightMultiplierWhenInPortrait: CGFloat = 0.33 if self.traitCollection.userInterfaceIdiom == .pad { collectionViewHeightMultiplierWhenInPortrait = 0.5 } // define collection view common, portrait and landscape constraints commonConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0)) commonConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0)) commonConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0)) portraitConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: collectionViewHeightMultiplierWhenInPortrait, constant: 0)) landscapeConstraints.append(NSLayoutConstraint(item: collectionView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: 1.0, constant: 0)) // define temp image view common, portrait and landscape constraints commonConstraints.append(NSLayoutConstraint(item: tempImageView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0)) commonConstraints.append(NSLayoutConstraint(item: tempImageView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0)) commonConstraints.append(NSLayoutConstraint(item: tempImageView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0)) portraitConstraints.append(NSLayoutConstraint(item: tempImageView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: collectionViewHeightMultiplierWhenInPortrait, constant: 0)) landscapeConstraints.append(NSLayoutConstraint(item: tempImageView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: 1.0, constant: 0)) // activate common constriants NSLayoutConstraint.activate(commonConstraints) } if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass { NSLayoutConstraint.deactivate(landscapeConstraints) NSLayoutConstraint.deactivate(portraitConstraints) if traitCollection.verticalSizeClass == .regular { self.navigationController?.setNavigationBarHidden(false, animated: true) NSLayoutConstraint.activate(portraitConstraints) } else { self.navigationController?.setNavigationBarHidden(true, animated: true) NSLayoutConstraint.activate(landscapeConstraints) } self.collectionView.collectionViewLayout.invalidateLayout() } } And finally, let’s update our viewWillTransitionToSize() method to do the swapping trick MainController.swift12345678910111213141516171819202122232425262728293031323334override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) print(\"before\") let visiblePage = self.collectionView.contentOffset.x / self.collectionView.bounds.size.width self.tempImageView.image = self.images[Int(visiblePage)] self.collectionView.isHidden = true self.tempImageView.isHidden = false coordinator.animate(alongsideTransition: { (context) in print(\"during\") let newOffset = CGPoint(x: visiblePage * self.collectionView.bounds.size.width, y: self.collectionView.contentOffset.y) self.collectionView.contentOffset = newOffset self.collectionView.collectionViewLayout.invalidateLayout() }) { (context) in print(\"after\") self.tempImageView.isHidden = true self.collectionView.isHidden = false } } And that’s it. Now we have a much nicer UICollectionView with AutoSizing support for iPhone and iPad orientation changes. Download full code here","categories":[{"name":"Development","slug":"Development","permalink":"http://villyg.com/categories/Development/"}],"tags":[{"name":"iOS","slug":"iOS","permalink":"http://villyg.com/tags/iOS/"},{"name":"Swift","slug":"Swift","permalink":"http://villyg.com/tags/Swift/"}]},{"title":"Adding image on top of UITableView","slug":"Adding-image-on-top-of-UITableView","date":"2017-02-28T01:36:18.000Z","updated":"2017-03-19T15:56:16.000Z","comments":true,"path":"2017/02/27/Adding-image-on-top-of-UITableView/","link":"","permalink":"http://villyg.com/2017/02/27/Adding-image-on-top-of-UITableView/","excerpt":"As part of version 1.2 of Gun Vault I decided to include the functionality of having the primary image of a gun included on top of the Gun Details screen. I wanted the image to take 1/3 of the screen when the device is in portrait mode … and full screen when in landscape mode. I thought this would be a simple task until I ended up spending over 2 hrs trying to fine-tune it so I figured I’d write about it.","text":"As part of version 1.2 of Gun Vault I decided to include the functionality of having the primary image of a gun included on top of the Gun Details screen. I wanted the image to take 1/3 of the screen when the device is in portrait mode … and full screen when in landscape mode. I thought this would be a simple task until I ended up spending over 2 hrs trying to fine-tune it so I figured I’d write about it. Set upWe’ll keep it very simple - 1 grouped-style table with 3 sections and 5 rows inside each section. 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import UIKitclass ViewController: UITableViewController { convenience init() { self.init(style: UITableViewStyle.grouped) } override func viewDidLoad() { super.viewDidLoad() self.navigationItem.title = \"Gun details\" } // *********************************************** // MARK: - UITableViewDataSource // *********************************************** override func numberOfSections(in tableView: UITableView) -> Int { return 3 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 5 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // since it is a static view we don't care about recycling at this time let result: UITableViewCell = UITableViewCell(style: .value1, reuseIdentifier: \"cell\") result.textLabel?.text = \"Text for row at: \\(indexPath.row)\" result.detailTextLabel?.text = \"Details for row at: \\(indexPath.row)\" return result } } Primary imageIn order to add a primary image on top we need to override 2 methods: heightForHeaderInSection and viewForHeaderInSection. The first one controlls the height of placeholder we are going to use for the image and the second one provides the actual image to be displayed. Note that we are not using the device orientation but the statusBar orientation instead. This is because while the device can be rotated - the interface could be locked for rotation in the same time. 1234567891011121314151617override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section == 0 { let orientation = UIApplication.shared.statusBarOrientation if orientation.isLandscape { return view.frame.height } else { return view.frame.height / 3 } } return UITableViewAutomaticDimension } 123456789101112131415161718override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { // We'll assume that there is only one section for now. if section == 0 { let imageView: UIImageView = UIImageView() imageView.clipsToBounds = true imageView.contentMode = .scaleAspectFill imageView.image = UIImage(named: \"gun\")! return imageView } return nil } Rotation supportThe last thing that is left to add it the rotation support. Note that there are 3 phases that allow hooks. For our purpose we’ll use the before rotation and during rotation to invalidate the table layout. My first try was actually adding tableView.reloadData() instead but it resulted in a flicker of the screen so beginUpdates() and endUpdates() ended up being the better choice. 12345678910111213141516171819202122override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) // print(\"will execute before rotation\") self.tableView.beginUpdates() coordinator.animate(alongsideTransition: { (context: UIViewControllerTransitionCoordinatorContext) in // print(\"will execute during rotation\") self.tableView.endUpdates() }) { (context: UIViewControllerTransitionCoordinatorContext) in // print(\"will execute after rotation\") } } That’s it. Now you have a nice table view with a smart primary image. Download full code here","categories":[{"name":"Development","slug":"Development","permalink":"http://villyg.com/categories/Development/"}],"tags":[{"name":"iOS","slug":"iOS","permalink":"http://villyg.com/tags/iOS/"},{"name":"Swift","slug":"Swift","permalink":"http://villyg.com/tags/Swift/"}]}]}