1

I had I previuosly question here How to make full screen background image inside ScrollView and keep aspect ratio and get very good aswer from "ekscrypto", thank you again.

enter image description here

I have buttons with text on the image (1, 2, 3, 4, 5 etc.) previously i used hard coded X and Y coordinates to position UI elemnts (buttons) on the background image, this solution worked on all iPhone devices, not on iPads. I changed my code according to the solution I received from "ekscrypto", and now of course this solution not work on any device.

On the image there is a road, and I need to arrange these buttons on this road. How can I properly position this buttons relative to the image, regardless of the device and image scale?

P.S. Ekscrypto also provided solution for the UI element positioning, but I don't understand how it works.

Here's how I currently attempt to create the buttons:

let imageOne = UIImage(named: "level1") as UIImage?
let levelOne = UIButton(type: UIButtonType.system) 
levelOne.frame = CGRect.init(x: 10, y: 10, width: 100, height: 45)
levelOne.setImage(imageOne, for: .normal) 
scrollView.addSubview(levelOne)

But the iPhone and iPad button positions and sizes should be different. How can I have them placed properly relative to the image?

Thank you so much ekscrypto, and sorry for delay with answer. Your code is working, and solves my problem, but there is a small problems.

  1. Had to change this line let button = UIButton(type: .system) to .custom, or instead of background image you get button that is filled with blue color.

  2. Button with background image is too big, specially on iPhone 5, changed let backgroundDesignHeight: CGFloat = 330.0 to 730 to make it smaller

  3. All buttons are in same place on iPhone and iPads, except «plus devices» there is a small offset to the bottom(down) button should be slightly higher

  4. On some devices background image on button are little bit blurry, this happened after I changed backgroundDesignHeight to 730
dakath
  • 79
  • 10
  • No iOS guys who can help here? – dakath Aug 14 '18 at 07:53
  • Since your UI is created in code, would you be able to show how you currently attempt to create the buttons? – ekscrypto Aug 14 '18 at 15:31
  • ekscrypto thank you for your help. here is the code: let imageOne = UIImage(named: "level1") as UIImage? let levelOne = UIButton(type: UIButtonType.system) levelOne.frame = CGRect.init(x: 10, y: 10, width: 100, height: 45) levelOne.setImage(imageOne, for: .normal) scrollView.addSubview(levelOne) – dakath Aug 14 '18 at 15:43
  • I added my answer below. Let me know if it requires further clarification or if it is giving you trouble – ekscrypto Aug 15 '18 at 11:50
  • If you want to avoid having blurry images, you will want to create custom button images properly sized for the various devices. Using a single image and resizing it in code has that effect of sometime not looking great. If you only have one set of images for your button, make it larger so when it is scaled it has more data to play with. – ekscrypto Aug 15 '18 at 15:01
  • As per my comment, the "330.0" was expected to be changed to whatever you actually use for the height of your background image. I suspect if 730.0 gave you a decent size, that your background is likely around that height. – ekscrypto Aug 15 '18 at 15:02
  • Got it, thanks again for the help – dakath Aug 15 '18 at 15:05

2 Answers2

1

You can solve this a few ways:

  • Compute the final x/y position manually based on current screen dimension and "design" dimension
  • Manually create aspect-ratio based constraints and let iOS compute the final position for you

Assuming that your device/app orientation is constant (always landscape or always portrait) and is always fullscreen, it may be easier to compute it by hand.

First, you will need a helper function to resize your image:

private func scaledImage(named imageName: String, scale: CGFloat) -> UIImage? {
    guard let image = UIImage(named: imageName) else { return nil }
    let targetSize = CGSize(width: image.size.width * scale, height: image.size.height * scale)
    return resizeImage(image: image, targetSize: targetSize)
}

// Adapted from https://stackoverflow.com/questions/31314412/how-to-resize-image-in-swift
private func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage? {
    let size = image.size

    let widthRatio  = targetSize.width  / size.width
    let heightRatio = targetSize.height / size.height

    // Figure out what our orientation is, and use that to form the rectangle
    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)
    }


    // This is the rect that we've calculated out and this is what is actually used below
    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()
    return newImage
}

Next, we will create a function to compute the location of the button, and the size of the button based on the image size:

private func positionScaledButton(x designX: CGFloat, y designY: CGFloat, imageName: String) -> UIButton {

    // First, compute our "designScale"
    let screenHeight = UIScreen.main.bounds.size.height
    let backgroundDesignHeight: CGFloat = 330.0 // ** see below
    let designScale = screenHeight / backgroundDesignHeight


    // Create button
    let button = UIButton(type: .custom)

    // Get image to use, and scale it as required
    guard let image = scaledImage(named: imageName, scale: designScale) else { return button }

    button.frame = CGRect(x: designX * designScale, y: designY * designScale, width: image.size.width, height: image.size.height)
    button.setImage(image, for: .normal)
    scrollView.addSubview(button)
    return button
}

** The value "330.0" in the code above refers to the height of the background image in the scroller, from which the x/y coordinates of the button were measured.

Assuming button's top-left corner should be at x: 10, y: 10, for image "levelOne":

let levelOneButton = positionScaledButton(x: 10, y: 10, imageName: "imageOne")
// To do: addTarget event handler
ekscrypto
  • 3,718
  • 1
  • 24
  • 38
  • I tried get rid of blurry images, created different images for different devices, also tried very big image and even vector image, no luck, image was still blurry. As workaround I just changed image after button was created. – dakath Aug 16 '18 at 14:08
  • @dakath if you are using custom images for each device, you will want to get rid of the scaledImage and resizeImage functions and use your images directly when creating the button. – ekscrypto Aug 16 '18 at 14:09
  • I have trouble to set properly backgroundDesignHeight. My background image is 1242 px height, and positionScaledButton func return very small button( image), specially on iPhone 5. Do you have any ideas how to solve this? – dakath Aug 16 '18 at 14:17
  • Given the design you used for your background, I would venture that yes your buttons would be very small on a iPhone 5. You might have to come up with a different background image for such small devices although I wouldn't design for iPhone 5 anymore; smallest device actively sold & supported by Apple is the iPhone 6s I believe. – ekscrypto Aug 16 '18 at 14:20
  • Unfortunately, on iphone 6 and iPad button is also to small. What is the easiest solution? Perhaps it's best just to make background image height smaller, for exp. twice smaller? – dakath Aug 16 '18 at 14:30
  • @dakath yes, that would probably be the easiest. Have a background design that allows for larger buttons, so when things are scaled to these devices the buttons and such are of appropriate useable heights – ekscrypto Aug 16 '18 at 14:34
0

Use relative, rather than absolute points.

So, rather than saying button 1 is at (30, 150) in points, use the fraction of the screen size instead so it's at (0.0369, 0.4) - then use those fractions to create your auto layout constraints.

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • I don't get it. Could you please, explain in more details? Supose we have an iPhone 6 plus with screen size 414x736 points. On iPone 6 plus needed place have 30, 150 coordinate in points. 1. How to get ralative points? 2. How to use this relative points to create constraints with auto layout? – dakath Aug 13 '18 at 11:05
  • Simple division - relative positions given that coordinate are (30/414, 150/736) = `(0.0725, 0.2038)` – Ashley Mills Aug 13 '18 at 11:08
  • Ok, how to position button using relative coordinates? button1.centerXAnchor.constraint(equalTo: "equal to what?").isActive = true button1.centerYAnchor.constraint(equalTo: "equal to what?").isActive = true Thanks in advance. – dakath Aug 13 '18 at 11:20
  • @dakath No need to set it as a constraint, just set the position – George Aug 14 '18 at 16:22