Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to center crop an image in SwiftUI

I'm new to SwiftUI. I guess everyone is at this point. I've been an app developer for about 6 years now and I feel stupid asking this question on StackOverflow. But I looked everywhere. How do I center crop an image in an ImageView in SwiftUI?

I know there's an option to change the aspect ratio but I only see fit and fill. I just want the imageView to centerCrop(android term) the image. Does anybody know?

like image 997
Renegade Avatar asked Aug 29 '20 19:08

Renegade


People also ask

How do I change the width and height of an image in SwiftUI?

Image("name"). resizable(). scaledToFit() isn't bugged, though. So you can wrap your image in a view, adjust the frame of the view to whatever size you need, then scaledToFit() will then make the image as big as it can be while keeping aspect ratio.


Video Answer


2 Answers

Android's ImageView.ScaleType documentation describes CENTER_CROP as:

CENTER_CROP

Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding). The image is then centered in the view.

This is essentially what Aspect Fill Scaling (aka .scaledToFill()) does, except (surprisingly) Aspect Fill doesn't clip the parts that fall outside of the frame.

By making the image .resizable, and applying .scaledToFill(). the image will be scaled proportionally to fill its available frame leaving off the top and bottom or sides as necessary. .clipped() then removes the parts of the image outside of the frame.

Image("myImage")
    .resizable()
    .scaledToFill()
    .frame(width: 200, height: 200, alignment: .center)
    .clipped()

To make this more convenient, I created this extension of Image:

extension Image {
    func centerCropped() -> some View {
        GeometryReader { geo in
            self
            .resizable()
            .scaledToFill()
            .frame(width: geo.size.width, height: geo.size.height)
            .clipped()
        }
    }
}

To use the Image extension, just put it in a file in your project (a name like image-centercropped.swift will work nicely). Then just add .centerCropped() to any image you want to be center cropped.

Image("apolloimage").centerCropped()

It uses GeometryReader to figure out its frame so that it can crop the image correctly, which means you don't have to specify the frame to get proper clipping. You are free to size the image however you like using an explicit frame, or by just adding padding() and Spacer() to keep it nicely placed relative to other user interface items.

For example: If you want an image to fill the screen of the phone:

struct ContentView: View { 
    var body: some View {
        Image("apolloimage")
            .centerCropped()
            .edgesIgnoringSafeArea(.all)
    }
}

will nicely show the center of the image by scaling the image to show either the full height or the full width of the image and cropping the parts the hang over on the other dimension.


Demonstration:

Here's a demo that shows how the image is centered and cropped as the image grows. In this demo, the frame width is a constant 360 while the frame height varies from 50 to 700 as the slider advances to the right. At the beginning when the frame is short, the tops and bottoms of the image are cropped. As the frame exceeds the aspectRatio of the original image, the resulting image is centered but cropped on the left and right sides.

struct ContentView: View {
    
    @State private var frameheight: CGFloat = 50
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Image("apolloimage")
                .resizable()
                .scaledToFill()
                .frame(width: 360, height: self.frameheight)
                .clipped()
            Spacer()
            Slider(value: self.$frameheight, in: 50...700)
                .padding(.horizontal, 20)
        }
    }
}

or an equivalent test using .centerCropped():

struct ContentView: View {
    
    @State private var frameheight: CGFloat = 50
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Image("apolloimage")
                .centerCropped()
                .frame(width: 360, height: self.frameheight)
            Spacer()
            Slider(value: self.$frameheight, in: 50...700)
                .padding(.horizontal, 20)
        }
    }
}

Demo running on the simulator of .centerCropped scaling


Alternate Solution

Another way to make a center cropped image is to make the image an .overlay() of Color.clear. This allows Color.clear to establish the clipping bounds.

Color.clear
.overlay(
    Image("apolloimage")
    .resizable()
    .scaledToFill()
)
.clipped()

and the corresponding extension to Image looks like this:

extension Image {
    func centerCropped() -> some View {
        Color.clear
        .overlay(
            self
            .resizable()
            .scaledToFill()
        )
        .clipped()
    }
}
like image 55
vacawama Avatar answered Oct 21 '22 02:10

vacawama


I was able to crop the square centre of image for view like iPhone Photo app.

extension Image {
    func centerSquareCropped() -> some View {
        GeometryReader { geo in
            let length = geo.size.width > geo.size.height ? geo.size.height : geo.size.width
            self
                .resizable()
                .scaledToFill()
                .frame(width: length, height: length, alignment: .center)
                .clipped()
        }
    }
 }
like image 3
Neocoleoidea Avatar answered Oct 21 '22 02:10

Neocoleoidea