Chapter 30: Image Picker :: Pages 709, 712, 731 Exercises. My Solution Attempts

iOS Apprentice, 6th edition

Page 709 Exercise. My Solution Attempt

Exercise. See if you can rewrite the above logic to use a didSet property observer on the image instance variable. If you succeed, then placing the photo into image will automatically update the UIImageView, without needing to call show(image:).

I removed the show(_:) method declaration and moved it’s definition into a property observer on image. I then removed the call to show(_:) within imagePickerController(_:didFinishPickingMediaWithInfo:)

var image: UIImage? {
  didSet {
    /// Exercise 708 Setting photo automatically updates UIImageView
    imageView.image = image
    imageView.isHidden = false
    imageView.frame = CGRect(x: 10, y: 10,
                             width: 260, height: 260)
    addPhotoLabel.isHidden = true
  }
}

/// Tells the delegate that the user picked a still image or movie.
func imagePickerController(_ picker: UIImagePickerController,
     didFinishPickingMediaWithInfo info: [String : Any]) {
  image = info[UIImagePickerControllerEditedImage] as? UIImage
//  if let theImage = image {
//    show(image: theImage)
//  }
  dismiss(animated: true, completion: nil)
}

This modification to image affected future code on Page 725 where show(image: theImage) was called.

Chapter 30: Image Picker :: Edit the image :: Page 725

override func viewDidLoad() {
  super.viewDidLoad()

  if let location = locationToEdit {
    title = "Edit Location"
    if location.hasPhoto {
      if let theImage = location.photoImage {
        image = theImage // <-- changed code
      }
    }
  }
}

Page 712 Exercise. My Solution Attempt

Exercise. Make the height of the photo table view cell dynamic, depending on the aspect ratio of the image. This is a tough one! You can keep the width of the image view at 260 points. This should correspond to the width of the UIImage object. You get the aspect ratio by doing image.size.width / image.size.height. With this ratio you can calculate what the height of the image view and the cell should be.

I divided 260 (width) by the aspect ratio to get the value of what the new image height should be. Used that height to create the CGRect object that will be the new specs for imageView.frame

var image: UIImage? {
  didSet {
    /// Exercise 712 Dynamic table view cell height from image aspect ratio.
    imageView.image = image
    imageView.isHidden = false
    // Height computed var
    var height: Double {
      let width = imageView.image!.size.width
      let height = imageView.image!.size.height
      let ratio = Double(width / height)
      return 260 / ratio
    }
    imageView.frame = CGRect(x: 10, y: 10,
                             width: 260, height: height) // dynamic height
    addPhotoLabel.isHidden = true
  }
}

The matching case pattern where section == 1 returns the set imageView.frame.height and I added 20 points of bottom padding, to match the top padding present at build time - as long as imageView.isHidden == false.

image

Refactored code pg 712-713


/// Asks the delegate for the height to use for a row in a specified location.
override func tableView(_ tableView: UITableView,
           heightForRowAt indexPath: IndexPath) -> CGFloat {

  switch (indexPath.section, indexPath.row) {
  case (0, 0):
    return 88
  case (1, _):
    return imageView.isHidden ? 44 :
      imageView.frame.height + 20 // dynamic height + 20 padding
  case (2, 2):
    addressLabel.frame.size = CGSize(
      width: view.bounds.size.width - 115,
      height: 10000)
    addressLabel.sizeToFit()
    addressLabel.frame.origin.x = view.bounds.size.width -
                           addressLabel.frame.size.width - 15
    return addressLabel.frame.size.height + 20
  default:
    return 44
  }
}

Page 731 Exercise. My Solution Attempt

Exercise. Change the resizing function in the UIImage extension to resize using the “Aspect Fill” rules instead of the “Aspect Fit” rules. Both keep the aspect ratio intact but Aspect Fit keeps the entire image visible while Aspect Fill fills up the entire rectangle and may cut off parts of the sides. In other words, Aspect Fit scales to the longest side but Aspect Fill scales to the shortest side.

I made it so when aspectFit is set to true, Aspect Fit rules are applied
When aspectFit is set to false, Aspect Fill rules are applied - Getting the bigger ratio, scales to the shortest side - and clips off the longer side exceeding the bounds.
I checkmarked the property clipsToBounds so that the clipping occurred. Content mode remained as Center.
In UIImage+Resize.swift

func resized(withBounds bounds: CGSize) -> UIImage {
  // Calculates how big the image should be 
  // in order to fit inside the bounds rectangle.
  let horizontalRatio = bounds.width / size.width
  let verticalRatio = bounds.height / size.height
  
  /// Exercise 731 Resize using Aspect Fill rules.
  let aspectFit = false
  let ratio = aspectFit ?
    min(horizontalRatio, verticalRatio) : max(horizontalRatio, verticalRatio)
  
  let newSize = CGSize(width: size.width * ratio,
                       height: size.height * ratio)
  
  // Creates a new image context and draws the image into that.
  ...
}

NOTE In order to clip the excess length exceeding the set bounds of 52, I had to checkmark the UIImageView’s property clipsToBounds

image

Screen Shots

image

image

Cheers!
Rebecca

1 Like

It is worth noting that the magic width number 260 is derived as 320 - 60, where the 320 is the width of an iPhone SE. If only you had a way to find the width of the size of the view, you could put that in place of the 320, and your image would fill nicely even on an iPhone 8 Plus. :wink:

1 Like

FYI I updated original post to include my attempt at the final exercise found on page 731


Thank you for this information! :smiley: :+1:
My thought is that I should only need to adjust the frame width, because that is the view width within it’s superview - so it would change based on iPhone point size.
My understanding of bounds is that it’s within the frame, so it’s values would be relative to the frame, unknowing if the original size had been altered - and is updated and set when the frame is changed.

iPhone 8 Plus currently looks like...

image

:grimacing:

  var image: UIImage? {
    didSet {
      /// Exercise 712 Dynamic table view cell height from image aspect ratio.
      imageView.image = image
      imageView.isHidden = false
      
      // added this
      var width: CGFloat {
        return view.frame.width - 60
      }
      
      var height: CGFloat {
        let imageWidth = imageView.image!.size.width
        let imageHeight = imageView.image!.size.height
        let ratio = imageWidth / imageHeight
        return width / ratio
      }
      
      imageView.frame = CGRect(x: 10, y: 10,
                               width: width,  // dynamic width
                              height: height) // dynamic height
      addPhotoLabel.isHidden = true
    }
  }
Looks good on an iPhone 8 Plus Now!

image


I took a look at Locations...

image

:grimacing:
Now I’m wanting to adjust all the widths!
Although, for the labels, it’s probably easier to set it’s width in Autoresizing:
image

Thanks again Steve, I’m learning so much!
Rebecca

1 Like

If you can do autoresize on the label, you can do it on the imageView too. Then instead of view.frame.width - 60, you can just use imageView.frame.width. I like that even better; it is what you really want, and you could change the margin on the storyboard without changing any code.
For height, I end up with:

  let ratio = image.size.width / image.size.height
  return imageView.frame.width / ratio
1 Like

This topic was automatically closed after 166 days. New replies are no longer allowed.