Clip Image to Square in Swiftui

Clip image to square in SwiftUI

A ZStack will help solve this by allowing us to layer views without one effecting the layout of the other.

For the text:

.frame(minWidth: 0, maxWidth: .infinity) to expand the text horizontally to its parent's size

.frame(minHeight: 0, maxHeight: .infinity) is useful in other situations

As for the image:

.aspectRatio(contentMode: .fill) to make the image maintain its aspect ratio rather than squashing to the size of its frame.

.layoutPriority(-1) to de-prioritize laying out the image to prevent it from expanding its parent (the ZStack within the ForEach in our case).

The value for layoutPriority just needs to be lower than the parent views which will be set to 0 by default. We have to do this because SwiftUI will layout a child before its parent, and the parent has to deal with the child size unless we manually prioritize differently.

The .clipped() modifier uses the bounding frame to mask the view so you'll need to set it to clip any images that aren't already 1:1 aspect ratio.

    var body: some View {
HStack {
ForEach(0..<3, id: \.self) { index in
ZStack {
Image(systemName: "doc.plaintext")
.resizable()
.aspectRatio(contentMode: .fill)
.layoutPriority(-1)
VStack {
Spacer()
Text("yes")
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.white)
}
}
.clipped()
.aspectRatio(1, contentMode: .fit)
.border(Color.red)
}
}
}

Edit: While geometry readers are super useful I think they should be avoided whenever possible. It's cleaner to let SwiftUI do the work. This is my initial solution with a Geometry Reader that works just as well.

        HStack {
ForEach(0..<3, id: \.self) { index in
ZStack {
GeometryReader { proxy in
Image(systemName: "pencil")
.resizable()
.scaledToFill()
.frame(width: proxy.size.width)
VStack {
Spacer()
Text("yes")
.frame(width: proxy.size.width)
.background(Color.white)
}
}
}
.clipped()
.aspectRatio(1, contentMode: .fit)
.border(Color.red)
}
}

How to center crop an image in SwiftUI

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()
}
}

Clip image with semi-circle mask?

You can create own shape. Below is a simple demo variant (for square content).

private struct DemoClipShape: Shape {
func path(in rect: CGRect) -> Path {
Path {
$0.move(to: CGPoint.zero)
$0.addLine(to: CGPoint(x: rect.maxX, y: 0))
$0.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
$0.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.midY, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
$0.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.midY, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
$0.addLine(to: CGPoint.zero)
}
}
}

demo

And here is a sample of how it could be applied - due to padding we need to offset clipping (mask) to compensate locations (also it can be passed as constant into shape constructor, which will be more correct and accurate, but I leave it for you):

// used larger values for better visibility
Circle()
.fill(Color.blue)
.frame(width: 148, height: 148)
.padding(.top, 18)
.padding(.bottom, 10)
.overlay(
Image("product-sample1")
.resizable()
.scaledToFit()
)
.mask(DemoClipShape().offset(x: 0, y: -11)) // << here !!

gives

Sample Image

SwiftUI - How to apply aspectFit to image so it does not go outside of designated area

You need to clip it after set size, like

Image("BG")
.resizable()
.aspectRatio(contentMode: .fill)
.scaledToFill()
.frame(height: 200)
.clipped() // << here !!

Can't use rotation effect in clip shape in swiftUI

Diamond shaped clipped image

Like this?

You can encapsulate the image in a ZStack and rotate them opposite to each other. Then set the frame of the ZStack to match the image. Feel free to ask if anything is unclear!

ZStack {
Image("IMG_1544")
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.rotationEffect(.degrees(-45))
}
.frame(width: cos(.pi/4) * 200, height: sin(.pi/4) * 200)
.cornerRadius(8)
.rotationEffect(.degrees(45))

Cropping center square of UIImage

I think here would be the perfect solution!
It is NOT good idea to crop image basis on the toSize's size. It will look weird when the image resolution (size) is very large.
Following code will crop the image as per the toSize's ratio.

Improved from @BlackRider's answer.

- (UIImage *)imageByCroppingImage:(UIImage *)image toSize:(CGSize)size
{
double newCropWidth, newCropHeight;

//=== To crop more efficently =====//
if(image.size.width < image.size.height){
if (image.size.width < size.width) {
newCropWidth = size.width;
}
else {
newCropWidth = image.size.width;
}
newCropHeight = (newCropWidth * size.height)/size.width;
} else {
if (image.size.height < size.height) {
newCropHeight = size.height;
}
else {
newCropHeight = image.size.height;
}
newCropWidth = (newCropHeight * size.width)/size.height;
}
//==============================//

double x = image.size.width/2.0 - newCropWidth/2.0;
double y = image.size.height/2.0 - newCropHeight/2.0;

CGRect cropRect = CGRectMake(x, y, newCropWidth, newCropHeight);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);

UIImage *cropped = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);

return cropped;
}


Related Topics



Leave a reply



Submit