Search code examples
iosswiftuiimageviewuiimage

How to stretch the bottom pixels of image to full screen that the content mode is scaleAspectFill on iOS?


I want to set an image as background that the content mode is scaleAspectFill and stretch the bottom pixel of line to fullscreen.

Here is a simple sample image that the size is 1280x300px that will be set in Assets as 2x. sample image 1

Here is a screenshot of the storyboard. I want to fill the the bottom pixel of line to the white space.

sample image 2

I have tried the Stretching and the Slicing feature but both effect are not what I want.

sample image 3 enter image description here

sample image 5

Here is the expected result what I want on iPhone 4S.

enter image description here


Solution

  • It's not clear how you are sizing your image...

    The 1280 x 300 image you posted, with the imageView constrained to the width of the view...

    AspectFit:

    enter image description here

    AspectFill (using the image's original 300 height):

    enter image description here

    AspectFill (using 150 height):

    enter image description here

    However, assuming the text in your images will have varying lengths, you may wind up with...

    AspectFit:

    enter image description here

    AspectFill (using 150 height):

    enter image description here

    However... assuming you have a plan for solving that, one approach would be to use two image views.

    enter image description here

    The top image view would hold your full image as you already have it... using .scaleAspectFill and constraining its height to show just the centered text in the image.

    The bottom image view would be constrained to the bottom of the view, with .scaleToFill, and you could use this extension:

    extension UIImage {
    
        // return the CGRect portion as a new UIImage
        func subImage(in rect: CGRect) -> UIImage? {
            let scale = UIScreen.main.scale
            guard let cgImage = cgImage else { return nil }
            guard let crop = cgImage.cropping(to: rect) else { return nil }
            return UIImage(cgImage: crop, scale: scale, orientation:.up)
        }
    
    }
    
    // then, use it like this
    let bottomImage = fullImage.subImage(in: CGRect(x: 0.0, y: fullImage.size.height - 1.0, width: 8.0, height: 1.0))
    

    to get an 8px x 1px portion of the original image. Set the .image of the bottom image view to that image and it will scale to fill the entire frame.

    Result:

    enter image description here

    and here is a complete example, using your original image (I named it "wake.png"):

    extension UIImage {
    
        // return the CGRect portion as a new UIImage
        func subImage(in rect: CGRect) -> UIImage? {
            let scale = UIScreen.main.scale
            guard let cgImage = cgImage else { return nil }
            guard let crop = cgImage.cropping(to: rect) else { return nil }
            return UIImage(cgImage: crop, scale: scale, orientation:.up)
        }
    
    }
    
    class StretchBottomViewController: UIViewController {
    
        var topImageView: UIImageView = {
            let v = UIImageView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.contentMode = .scaleAspectFill
            v.setContentHuggingPriority(.required, for: .vertical)
            return v
        }()
    
        var bottomImageView: UIImageView = {
            let v = UIImageView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.contentMode = .scaleToFill
            return v
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.addSubview(topImageView)
            view.addSubview(bottomImageView)
    
            let g = view.safeAreaLayoutGuide
    
            NSLayoutConstraint.activate([
    
                // constrain top image view to top / leading / trailing at Zero
                topImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                topImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
                topImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
    
                // constrain bottom image view to bottom of top image view / leading / trailing at Zero
                bottomImageView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: 0.0),
                bottomImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
                bottomImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
    
                // constrain bottom image view to bottom at Zero
                bottomImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
    
                // however you are determining the height of the top image view
                // using 150 here to work with your original image
                topImageView.heightAnchor.constraint(equalToConstant: 150.0),
    
            ])
    
            // load "wake me up" image
            if let fullImage = UIImage(named: "wake") {
                // get the bottom part of the full image (8 pixels wide, 1-pixel tall)
                if let bottomImage = fullImage.subImage(in: CGRect(x: 0.0, y: fullImage.size.height - 1.0, width: 8.0, height: 1.0)) {
                    topImageView.image = fullImage
                    bottomImageView.image = bottomImage
                }
            }
    
        }
    
    }