UIImage with the corner round itself - ios

Due to the auto layout feature, I tried to use UIImage corner radius instead of UIImageView. The corner radius was too thin when the photo was too large, such as 4k x 4k, but when the photo was small, such as 500 x 500, the corner radius was too large. No matter what size the photo is, I want the corner radius to be 25. Do you have any suggestions?
I tried the following code from this, but it does not solve my problem.:
https://newbedev.com/how-to-set-corner-radius-to-uiimage-not-uiimageview-in-ios-swift
My goal is to have the corner radius equal to the size of any photo. I tested the image name "demo" which is 4,000 x 4,000, the corner radius is like 5, and "demo2" which is 500 x 500, the corner radius is like 50.
Here my full code:
class TheCountdownDetails: UIViewController {
let photoPreview = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
photoPreview.translatesAutoresizingMaskIntoConstraints = false
photoPreview.contentMode = .scaleAspectFit
view.addSubview(photoPreview)
photoPreview.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
photoPreview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
photoPreview.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
photoPreview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
}
override func viewDidLayoutSubviews() {
photoPreview.image = UIImage(named: "demo")?.withRoundedCorners(radius: 25)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
}
extension UIImage {
public func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? {
let maxRadius = min(size.width, size.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let rect = CGRect(origin: .zero, size: size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

If you use debug to inspect the UIImage returned from your withRoundedCorners(...) func, you'll see that both images do, in fact, have the same rounded corners.
The problem is that you are using a radius of 25 on a 4k x 4k image, and a radius of 25 on a 500 x 500 image, but then scaling them to fit your imageView.
If you change your imageView's content mode to:
photoPreview.contentMode = .topLeft
the images won't scale, and you'll see that you're getting the same radius rounded corners.
So, you need to scale the image at the same time you're clipping the rounded corners.
Here's a modification of your extension:
extension UIImage {
func withRoundedCorners(radius: CGFloat? = nil, targetSize: CGSize) -> UIImage {
// First, determine the scale factor that preserves aspect ratio
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
let scaleFactor = min(widthRatio, heightRatio)
// Compute the new image size that preserves aspect ratio
let scaledImageSize = CGSize(
width: size.width * scaleFactor,
height: size.height * scaleFactor
)
let maxRadius = min(scaledImageSize.width, scaledImageSize.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
let newRect: CGRect = CGRect(origin: .zero, size: scaledImageSize)
let renderer = UIGraphicsImageRenderer(size: newRect.size)
let scaledImage = renderer.image { _ in
UIBezierPath(roundedRect: newRect, cornerRadius: cornerRadius).addClip()
self.draw(in: newRect)
}
return scaledImage
}
}
and an example controller, putting two imageViews in a stack view, so we can see two different size images at the same time:
class TheCountdownDetails: UIViewController {
let photoPreview1 = UIImageView()
let photoPreview2 = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillEqually
stack.spacing = 20
stack.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(photoPreview1)
stack.addArrangedSubview(photoPreview2)
view.addSubview(stack)
photoPreview1.contentMode = .center
photoPreview2.contentMode = .center
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// image views are in a stack view,
// so we need to force their layouts
// before asking for their frames
photoPreview1.setNeedsLayout()
photoPreview1.layoutIfNeeded()
photoPreview2.setNeedsLayout()
photoPreview2.layoutIfNeeded()
guard let img1 = UIImage(named: "image4kx4k") else { return }
guard let img2 = UIImage(named: "image500x500") else { return }
let img1r = img1.withRoundedCorners(radius: 25, targetSize: photoPreview1.frame.size)
let img2r = img2.withRoundedCorners(radius: 25, targetSize: photoPreview2.frame.size)
photoPreview1.image = img1r
photoPreview2.image = img2r
}
}
Using this 4kx4k image (original source: https://images.wallpaperscraft.com/image/single/night_city_aerial_view_city_lights_130879_4000x4000.jpg):
and this 500x500 image (original source: https://www.digitalphotopix.com/wp-content/uploads/2011/02/blue-lake.jpg)
We get this output:

Related

I have an UIImageView in an UIScrollView. How do I center at x,y of the image programmatically?

I have an UIImageView in an UIScrollView, and the image has size say 2000x3000. I can pinch to zoom and drag to pan on the screen. (I followed this video https://www.youtube.com/watch?v=0Tz0vI721c8).
My question is if I want to center the display at a point on the image programmatically (i.e. 600, 800), I think I need to use scrollView.setContentOffset(CGPoint), how do I figure out the CGPoint?
You have to take a couple things into account, beyond just setting the .contentOffset to match the "target center point" ...
First, the goal...
Using this 2000 x 3000 image:
"
With this set of "center points":
let points: [CGPoint] = [
CGPoint(x: 100, y: 100),
CGPoint(x: 800, y: 600),
CGPoint(x: 1600, y: 1200),
CGPoint(x: 600, y: 2000),
CGPoint(x: 1900, y: 2900),
]
We want to programmatically set the centers to the center of the scroll view:
If all we did was set the .contentOffset to the "target point" for #3, we'd get this:
So, we need to subtract one-half of the width and height of the scroll view frame:
scrollView.contentOffset.x = points[2].x - (scrollView.frame.width * 0.5)
scrollView.contentOffset.y = points[2].y - (scrollView.frame.height * 0.5)
That puts our "target point" in the center of the scroll view, but...
If we've zoomed out to, say, 90% (scrollView.zoomScale = 0.9), we get this:
So, we need to translate the "target point" to the zoom scale:
// translate target point to zoomScale
var x: CGFloat = points[2].x * scrollView.zoomScale
var y: CGFloat = points[2].y * scrollView.zoomScale
x = x - (scrollView.frame.width * 0.5)
y = y - (scrollView.frame.height * 0.5)
scrollView.contentOffset.x = x
scrollView.contentOffset.y = y
and, woo hoo, we have this:
The next problem, though, is that we don't want to center the "target point" if that would exceed the scroll view limits.
For example, if we try to center on "1" or "5" we get:
and:
the Points are centered in the scroll view, but as soon as we touch it to scroll or zoom, it will snap to the corner.
We need to limit the .contentOffset to avoid that.
So, for point "1" (points[0]):
// translate target point to zoomScale
var x: CGFloat = points[0].x * scrollView.zoomScale
var y: CGFloat = points[0].y * scrollView.zoomScale
// don't want to set offset below Zero
let minOffset: CGPoint = .zero
// don't want to set offset greater than what will fit in the scroll view
let maxOffset: CGPoint = CGPoint(x: imgView.frame.width - scrollView.frame.width, y: imgView.frame.height - scrollView.frame.height)
// this prevents x and from being negative
x = max(minOffset.x, x - (scrollView.frame.width * 0.5))
y = max(minOffset.y, y - (scrollView.frame.height * 0.5))
// this prevents x and y from exceeding the frame of the scroll view
x = min(maxOffset.x, x)
y = min(maxOffset.y, y)
scrollView.contentOffset.x = x
scrollView.contentOffset.y = y
Here's a complete example... as in the above images, tapping a "Number Button" will scroll that center point to the center of the scroll view (or as close as possible, if it would exceed the limits). All you need is to add that 2000x3000 image (named "img2000x3000") ... no #IBOutlet or #IBAction connections needed:
class ScrollPosViewController: UIViewController, UIScrollViewDelegate {
let points: [CGPoint] = [
CGPoint(x: 100, y: 100),
CGPoint(x: 800, y: 600),
CGPoint(x: 1600, y: 1200),
CGPoint(x: 600, y: 2000),
CGPoint(x: 1900, y: 2900),
]
let scrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .yellow
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let imgView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
// make sure we can load the image
guard let img = UIImage(named: "img2000x3000") else {
print("Could not load image!!!")
return
}
// assing image to image view
imgView.image = img
// create a buttons stack view
let stack: UIStackView = {
let v = UIStackView()
v.distribution = .fillEqually
v.spacing = 20
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
for i in 0..<points.count {
let b = UIButton()
b.setTitle("\(i + 1)", for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.backgroundColor = .systemBlue
b.layer.cornerRadius = 8
b.addTarget(self, action: #selector(centerOn(_:)), for: .touchUpInside)
stack.addArrangedSubview(b)
}
// add image view to scroll view
scrollView.addSubview(imgView)
// add scroll view to view
view.addSubview(scrollView)
// add buttons stack view to view
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
let c = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// constrain background image view
// Leading / Trailing at 20-pts
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// height proportional to image size
scrollView.heightAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: img.size.height / img.size.width),
// centered vertically
scrollView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view to all 4 sides of scroll view's Content Layout Guide
imgView.topAnchor.constraint(equalTo: c.topAnchor),
imgView.leadingAnchor.constraint(equalTo: c.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: c.trailingAnchor),
imgView.bottomAnchor.constraint(equalTo: c.bottomAnchor),
// constrain image view's width/height to image width/height
imgView.widthAnchor.constraint(equalToConstant: img.size.width),
imgView.heightAnchor.constraint(equalToConstant: img.size.height),
// constrain buttons stack view at bottom
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// update min zoom scale so we can only "zoom out" until
// the content view fits the scroll view frame
if scrollView.minimumZoomScale == 1.0 {
let xScale = scrollView.frame.width / imgView.frame.width
let yScale = scrollView.frame.height / imgView.frame.height
scrollView.minimumZoomScale = min(xScale, yScale)
}
}
#objc func centerOn(_ sender: Any?) -> Void {
guard let btn = sender as? UIButton,
let t = btn.currentTitle,
let n = Int(t),
n > 0,
n <= points.count
else {
return
}
// translate target point to zoomScale
var x: CGFloat = points[n - 1].x * scrollView.zoomScale
var y: CGFloat = points[n - 1].y * scrollView.zoomScale
// don't want to set offset below Zero
let minOffset: CGPoint = .zero
// don't want to set offset greater than what will fit in the scroll view
let maxOffset: CGPoint = CGPoint(x: imgView.frame.width - scrollView.frame.width, y: imgView.frame.height - scrollView.frame.height)
// this prevents x and from being negative
x = max(minOffset.x, x - (scrollView.frame.width * 0.5))
y = max(minOffset.y, y - (scrollView.frame.height * 0.5))
// this prevents x and y from exceeding the frame of the scroll view
x = min(maxOffset.x, x)
y = min(maxOffset.y, y)
// if we want to animate the point to the new offset
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveEaseInOut], animations: {
// set the new content offset
self.scrollView.contentOffset = CGPoint(x: x, y: y)
}, completion: nil)
// or, without animation
// set the new content offset
//scrollView.contentOffset = CGPoint(x: x, y: y)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgView
}
}
// Assuming your image is the same width and height of
// the scrollview, the CGPoint for the center of the image will be
let scrollViewCenterX: CGFloat = scrollView.bounds.width / 2
let scrollViewCenterY: CGFloat = scrollView.bounds.height / 2
let imageCenterPoint = CGPoint(x: scrollViewCenterX, y: scrollViewCenterY)
scrollView.setContentOffset(imageCenterPoint)
// if we want to center the scrollview on `point`
let point = CGPoint(x: 600, y: 800)
let newPoint = CGPoint(x: point.x / 2, y: point.y / 2)
scrollView.setContentOffset(newPoint)

How to crop the shape of an image out of a caShape layer,

I have a cashape layer which looks like this
I have this P Image
I want the center of the triangle to be see-through in the same shape of P image (or any image) kind of like this
I have tried (among many many other things) this... which seems close, but it ONLY shows the P, I need it to show everything but the P
let viewTheTriangleIsIn = UIView()
let pImage = UIImageView()
let maskLayer = CAShapeLayer()
maskLayer.frame.origin = CGPoint(x: 0, y: 0)
maskLayer.frame.size = viewTheTriangleIsIn.bounds.size
maskLayer.contents = pImage.image!.cgImage
maskLayer.fillRule = .evenOdd
viewTheTriangleIsIn.layer.mask = maskLayer
Assuming your "P image (or any image)" has transparent (alpha) areas such as this (the P is surrounded by transparency):
so it would look like this in a UIImageView:
You can create an "inverted transparency" image:
guard let pImage = UIImage(named: "pImage") else {
print("Could not load mask image!")
return
}
// size of view you want to mask
let sz: CGSize = CGSize(width: 200, height: 200)
// rect to draw the mask image (where we want the "P")
let maskRect:CGRect = CGRect(x: 50, y: 100, width: 100, height: 80)
let renderer = UIGraphicsImageRenderer(size: sz)
let iMaskImage = renderer.image { ctx in
// fill with black (any color other than clear will do)
ctx.cgContext.setFillColor(UIColor.black.cgColor)
ctx.fill(CGRect(origin: .zero, size: sz))
// draw the image in maskRect with .xor blendMode
// this will make all non-transparent pixels in the image transparent
pImage.draw(in: maskRect, blendMode: .xor, alpha: 1.0)
}
At that point, iMaskImage will be this (the white "P" shape is not white... it's transparent):
You can then use that image as a mask on any other view.
Here's an example MaskedTriangleView -- it uses a CAShapeLayer for the triangle, and then uses the "P" image as a layer mask:
class MaskedTriangleView: UIView {
let shapeLayer = CAShapeLayer()
var maskImage: UIImage?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
layer.addSublayer(shapeLayer)
shapeLayer.fillColor = UIColor.systemYellow.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 1
}
override func layoutSubviews() {
super.layoutSubviews()
// create a triangle shape path
let bez: UIBezierPath = UIBezierPath()
bez.move(to: CGPoint(x: bounds.minX, y: bounds.maxY))
bez.addLine(to: CGPoint(x: bounds.midX, y: bounds.minY))
bez.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
bez.close()
shapeLayer.path = bez.cgPath
if let img = maskImage {
// let's make our mask image
// 50% of the height of self
let h: CGFloat = bounds.height * 0.5
// 40% from the Top (leaving 10% space at the bottom)
let y: CGFloat = bounds.height * 0.4
// keep it proportionally sized
let ratio: CGFloat = h / img.size.height
// width is proportional to height
let w: CGFloat = img.size.width * ratio
// center horizontally
let x: CGFloat = (bounds.width - w) * 0.5
// rect to draw the mask image
let maskRect:CGRect = CGRect(x: x, y: y, width: w, height: h)
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(size: bounds.size)
let iMaskImage: UIImage = renderer.image { ctx in
// fill with black (any color other than clear will do)
ctx.cgContext.setFillColor(UIColor.black.cgColor)
ctx.fill(CGRect(origin: .zero, size: bounds.size))
// draw the image in maskRect with .xor blendMode
// this will make all non-transparent pixels in the image transparent
img.draw(in: maskRect, blendMode: .xor, alpha: 1.0)
}
// create a layer
let maskLayer: CALayer = CALayer()
// set the new image as its contents
maskLayer.contents = iMaskImage.cgImage
// same frame as self
maskLayer.frame = bounds
// use it as the mask for the shape layer
shapeLayer.mask = maskLayer
}
}
}
and it will look like this (the top image is overlaid on an image view, the bottom image is added as a subview of the main view, with the green background showing through):
Here's the example controller:
class ImageMaskingVC: UIViewController {
var bkgImageView: UIImageView!
var triangleView1: MaskedTriangleView!
var triangleView2: MaskedTriangleView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
// make sure we can load the images
guard let bkimg = UIImage(named: "samplePic") else {
print("Could not load background image!")
return
}
guard let pImage = UIImage(named: "pImage") else {
print("Could not load mask image!")
return
}
// create the image view and set its image
bkgImageView = UIImageView()
bkgImageView.image = bkimg
// create a MaskedTriangleView and set its maskImage
triangleView1 = MaskedTriangleView()
triangleView1.maskImage = pImage
// create another MaskedTriangleView and set its maskImage
triangleView2 = MaskedTriangleView()
triangleView2.maskImage = pImage
// add the views
[bkgImageView, triangleView1, triangleView2].forEach {
if let v = $0 {
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain background image view
// Top / Leading / Trailing at 20-pts
bkgImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
bkgImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
bkgImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// height proportional to background image size
bkgImageView.heightAnchor.constraint(equalTo: bkgImageView.widthAnchor, multiplier: bkimg.size.height / bkimg.size.width),
// constrain first MaskedTriangleView exactly on top of the background image view
triangleView1.topAnchor.constraint(equalTo: bkgImageView.topAnchor, constant: 20.0),
triangleView1.leadingAnchor.constraint(equalTo: bkgImageView.leadingAnchor, constant: 20.0),
triangleView1.trailingAnchor.constraint(equalTo: bkgImageView.trailingAnchor, constant: -20.0),
triangleView1.bottomAnchor.constraint(equalTo: bkgImageView.bottomAnchor, constant: -20.0),
// constrain the second MaskedTriangleView below the background image view
// with same width and height as the first MaskedTriangleView
triangleView2.topAnchor.constraint(equalTo: bkgImageView.bottomAnchor, constant: 20.0),
triangleView2.widthAnchor.constraint(equalTo: triangleView1.widthAnchor, constant: 0.0),
triangleView2.heightAnchor.constraint(equalTo: triangleView1.heightAnchor, constant: 0.0),
// centered horizontally
triangleView2.centerXAnchor.constraint(equalTo: triangleView1.centerXAnchor, constant: 0.0),
])
}
}

Scale down UIView and it's subview simultaneously in swift 5?

I have created UIView and it has one subview and it's containing shape object which are creating using UIBezierPath, and UIView has fixed height and width (image 1). When i click the blue color button, it should be scale down to another fixed width and height. I have applied CGAffineTransform to subview and, i guess it is scale down properly, but since topAnchor and leftAchor has constant values, after scale down it is not displaying properly (image 2).I will attach my source code here and screenshots, please, analyze this and can someone suggest better approach for how to do this ? and highly appreciate ur feedback and comments.
code:
import UIKit
class ViewController: UIViewController {
var screenView: UIView!
var button: UIButton!
let widthOfScaleDownView: CGFloat = 200
let heightOfScaleDownView: CGFloat = 300
override func viewDidLoad() {
screenView = UIView(frame: .zero)
screenView.translatesAutoresizingMaskIntoConstraints = false
screenView.backgroundColor = UIColor.red
self.view.addSubview(screenView)
NSLayoutConstraint.activate([
screenView.heightAnchor.constraint(equalToConstant: 800),
screenView.widthAnchor.constraint(equalToConstant: 600),
screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
])
button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 50),
button.widthAnchor.constraint(equalToConstant: 100),
button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 950),
button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 350)
])
button.setTitle("click", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(scaleDownView(_:)), for: .touchUpInside)
let shapeView = UIView(frame: .zero)
shapeView.translatesAutoresizingMaskIntoConstraints = false
let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
let shape = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = shape.cgPath
layer.lineWidth = 2
shapeView.layer.addSublayer(layer)
screenView.addSubview(shapeView)
NSLayoutConstraint.activate([
shapeView.topAnchor.constraint(equalTo: screenView.topAnchor, constant: 250),
shapeView.leftAnchor.constraint(equalTo: screenView.leftAnchor, constant: 150)
])
}
#IBAction func scaleDownView(_ sender: UIButton) {
let widthScale = widthOfScaleDownView/screenView.frame.width
let heightScale = heightOfScaleDownView/screenView.frame.height
screenView.subviews.forEach{ view in
view.transform = CGAffineTransform(scaleX: widthScale, y: heightScale)
}
NSLayoutConstraint.activate([
screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView),
screenView.widthAnchor.constraint(equalToConstant: widthOfScaleDownView),
screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
])
}
}
Couple things...
1 - When you use CGAffineTransform to change a view, it will automatically affect the view's subviews. So no need for a loop.
2 - Transforms are cumulative, so when "going back" to the original scale, use .identity instead of another scale transform.
3 - You cannot set a constraint multiple times. So if you do:
screenView.heightAnchor.constraint(equalToConstant: 800)
and then you also do:
screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView)
you will get auto-layout conflicts... the view cannot be both 800-pts tall and
300-pts tall at the same time.
Take a look at the changes I made to your code. I also added a 1-second animation to the scale transform so you can see what it's doing:
class ViewController: UIViewController {
var screenView: UIView!
var button: UIButton!
let widthOfScaleDownView: CGFloat = 200
let heightOfScaleDownView: CGFloat = 300
override func viewDidLoad() {
screenView = UIView(frame: .zero)
screenView.translatesAutoresizingMaskIntoConstraints = false
screenView.backgroundColor = UIColor.red
self.view.addSubview(screenView)
// constrain screenView
// width: 800 height: 600
// centered X and Y
NSLayoutConstraint.activate([
screenView.heightAnchor.constraint(equalToConstant: 800),
screenView.widthAnchor.constraint(equalToConstant: 600),
screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
])
button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 50),
button.widthAnchor.constraint(equalToConstant: 100),
button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 950),
button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 350)
])
button.setTitle("click", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(scaleDownView(_:)), for: .touchUpInside)
let shapeView = UIView(frame: .zero)
shapeView.translatesAutoresizingMaskIntoConstraints = false
let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
let shape = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = shape.cgPath
layer.lineWidth = 2
shapeView.layer.addSublayer(layer)
screenView.addSubview(shapeView)
// constrain shapeView
// width: 300 height: 300
// centered X and Y in screenView
NSLayoutConstraint.activate([
shapeView.widthAnchor.constraint(equalToConstant: 300.0),
shapeView.heightAnchor.constraint(equalToConstant: 300.0),
shapeView.centerXAnchor.constraint(equalTo: screenView.centerXAnchor),
shapeView.centerYAnchor.constraint(equalTo: screenView.centerYAnchor),
])
}
#IBAction func scaleDownView(_ sender: UIButton) {
let widthScale = widthOfScaleDownView/screenView.frame.width
let heightScale = heightOfScaleDownView/screenView.frame.height
UIView.animate(withDuration: 1.0) {
// if we have already been scaled down
if widthScale == 1 {
// animate the scale transform back to original (don't add another transform)
self.screenView.transform = .identity
} else {
// animate the scale-down transform
self.screenView.transform = CGAffineTransform(scaleX: widthScale, y: heightScale)
}
}
// do NOT change screenView constraints!
}
}

Can I resize just the image of an imageView?

So I have this code:
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.borderWidth = 5
imageView.layer.borderColor = UIColor.green.cgColor
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 200),
imageView.widthAnchor.constraint(equalToConstant: 200),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
])
let image = UIImage(named: "3")
//let image = UIImage(named: "3")?.resizeImageWith(newSize: CGSize(width: 5,height: 5))
imageView.image = image
}
That looks like this:
Im trying to resize just the image ( so like from the green border there is a margin) with this extension:
func resizeImageWith(newSize: CGSize) -> UIImage {
let horizontalRatio = newSize.width / size.width
let verticalRatio = newSize.height / size.height
let ratio = min(horizontalRatio, verticalRatio)
let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: newSize))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
But when I use that like this:
let image = UIImage(named: "3")?.resizeImageWith(newSize: CGSize(width: 60,height: 60))
I get the following result:
And same with this:
let image = UIImage(named: "3")?.resizeImageWith(newSize: CGSize(width: 5,height: 5))
The image just get worst.
Is it possible to just resize the image in order to have a margin from the imageView? Thank you!
Change the image view content mode to Center:
imageView.contentMode = .center
I was able to produce what you wanted, but doing away with constraints. Using a container view that holds the imageView as a subview, you can use the container like a frame and set its border width and color.
With the frame in place, you can then resize the image as per your needs by just setting the frame of the imageView. The last 2 lines of the code are commented out, but if you uncomment them, you will see that the image will shrink to fit the new imageView frame size set to (50, 50).
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let containerView = UIView()
containerView.layer.borderWidth = 5
containerView.layer.borderColor = UIColor.green.cgColor
containerView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
containerView.center = view.center
let imageView = UIImageView()
view.addSubview(containerView)
containerView.addSubview(imageView)
let image = UIImage(named: "3")!
imageView.frame = CGRect(origin: imageView.frame.origin, size: CGSize(width: 200, height: 200))
imageView.image = image
imageView.contentMode = .scaleAspectFit
imageView.center = view.convert(containerView.center, to: containerView)
print("Image Width: \(image.size.width)")
print("Image Height: \(image.size.height)")
print("ImageView Width: \(imageView.frame.width)")
print("ImageView Height: \(imageView.frame.height)")
print("Container Width: \(containerView.frame.width)")
print("Container Height: \(containerView.frame.height)")
print("View Center: \(view.center.x),\(view.center.y)")
print("Container Center: \(containerView.center.x),\(containerView.center.y)")
//imageView.frame.size = CGSize(width: 50, height: 50)
//imageView.center = view.convert(containerView.center, to: containerView)
}
}
I used .scaleAspectFit so that the image will be forced to fit whatever size you set the imageView to, while maintaining its original aspect ratio. You can of course change this to suit your needs as well. If you need to set the size of your image in different ways, you'll need to change the imageView's contentMode property as appropriate. More info here:
https://developer.apple.com/documentation/uikit/uiimageview
The convert function just helps to layout the imageView where you want it, within its parent view.
The print debug statements are helpful for checking your layout, but of course, unnecessary.

how Add mirror image to 2d Box corners

hello friends i need to add mirror image in right corner and bottom.
i create the type of view with the help of this answer
how to create a view like this shape in swift?
but i am not able to add image in right corner and bottom
Current approach is somewhat different from the previous one.
Previously i've drawn the right and bottom corner and resized the image such that, the appearance is asked in that question.
But in this question, that approach won't work anymore. First reason is that draw(in rect: CGRect) does not provide mirroring functionality for image while drawing. iOS only provide mirroring functionality while drawing in UIImageView. So to do the mirroring we need to setup 3 image view.
So the approach to achieve it is following
Put one UIImageView in center.
Put one UIImageView in the right of the center another to the bottom.
Now calculate the right mirrored image and apply shear the right image view.
Do the same for the bottom.
One problem still remain in the approach described above. For example we shear the right image view according to y axis. The shear operation works along with center. So the left side and right side both shears across the y axis. So we translate positive to the x axis so that all the shear applies to the right of UIImageView. Thats why overlap the right and main image view to fill the gap between to, as following
rightImageView.leadingAnchor.constraint(equalTo: mainImageView.trailingAnchor, constant: -stripSize / 2),
Same goes for the bottom image view.
Code
lass ViewController: UIViewController {
let mainImageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
let rightImageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
let bottomImageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
let rightDarkView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.4)
return view
}()
let bottomDarkView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.5)
return view
}()
let mainImageSize = CGSize(width: 240, height: 240)
let stripSize = CGFloat(20)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
setupMirroView()
}
func setupView() {
view.addSubview(mainImageView)
view.addSubview(rightImageView)
view.addSubview(bottomImageView)
view.addSubview(rightDarkView)
view.addSubview(bottomDarkView)
NSLayoutConstraint.activate([
mainImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mainImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
mainImageView.widthAnchor.constraint(equalToConstant: mainImageSize.width),
mainImageView.heightAnchor.constraint(equalToConstant: mainImageSize.height),
rightImageView.leadingAnchor.constraint(equalTo: mainImageView.trailingAnchor, constant: -stripSize / 2),
rightImageView.topAnchor.constraint(equalTo: mainImageView.topAnchor),
rightImageView.bottomAnchor.constraint(equalTo: mainImageView.bottomAnchor),
rightImageView.widthAnchor.constraint(equalToConstant: stripSize),
rightDarkView.leadingAnchor.constraint(equalTo: rightImageView.leadingAnchor),
rightDarkView.topAnchor.constraint(equalTo: rightImageView.topAnchor),
rightDarkView.trailingAnchor.constraint(equalTo: rightImageView.trailingAnchor),
rightDarkView.bottomAnchor.constraint(equalTo: rightImageView.bottomAnchor),
bottomImageView.topAnchor.constraint(equalTo: mainImageView.bottomAnchor, constant: -stripSize / 2),
bottomImageView.leadingAnchor.constraint(equalTo: mainImageView.leadingAnchor),
bottomImageView.trailingAnchor.constraint(equalTo: mainImageView.trailingAnchor),
bottomImageView.heightAnchor.constraint(equalToConstant: stripSize),
bottomDarkView.leadingAnchor.constraint(equalTo: bottomImageView.leadingAnchor),
bottomDarkView.topAnchor.constraint(equalTo: bottomImageView.topAnchor),
bottomDarkView.trailingAnchor.constraint(equalTo: bottomImageView.trailingAnchor),
bottomDarkView.bottomAnchor.constraint(equalTo: bottomImageView.bottomAnchor)
])
}
func setupMirroView() {
let image = UIImage(named: "image")
mainImageView.image = image
// prepare the image for the right image view
let rightImage = image?.cropped(to: CGSize(width: stripSize, height: mainImageSize.height),
drawInto: CGRect(x: stripSize - mainImageSize.width, y: 0, width: mainImageSize.width, height: mainImageSize.height))
let rightImageMirrored = UIImage(cgImage: rightImage!.cgImage!, scale: 1.0, orientation: .upMirrored)
rightImageView.image = rightImageMirrored
var rightTransform = CGAffineTransform.identity
rightTransform = rightTransform.translatedBy(x: stripSize / 2, y: 0)
rightTransform = rightTransform.concatenating(CGAffineTransform(a: 1.0, b: 1.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0))
rightImageView.transform = rightTransform
rightDarkView.transform = rightTransform
// prepare the image for the left image view
let downImage = image?.cropped(to: CGSize(width: mainImageSize.width, height: stripSize),
drawInto: CGRect(x: 0, y: stripSize - mainImageSize.height, width: mainImageSize.width, height: mainImageSize.height))
let downImageMirroed = UIImage(cgImage: downImage!.cgImage!, scale: 1.0, orientation: .downMirrored)
bottomImageView.image = downImageMirroed
var downTransform = CGAffineTransform.identity
downTransform = downTransform.translatedBy(x: 0, y: stripSize / 2)
downTransform = downTransform.concatenating(__CGAffineTransformMake(1.0, 0.0, 1.0, 1.0, 0.0, 0.0))
bottomImageView.transform = downTransform
bottomDarkView.transform = downTransform
}
}
extension UIImage {
func cropped(to size: CGSize, drawInto: CGRect) -> UIImage {
UIGraphicsBeginImageContext(size)
self.draw(in: drawInto)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
output
One approach is to use 3 image views - the "main" imageView plus a right-side imageView and a bottom imageView.
Scale your image to fit the main view + a small amount for the right-side and bottom.
Set the main imageView's .contentMode = .topLeft to clip the right and bottom.
Set the right-side imageView to .topRight to clip the left and bottom.
Set the bottom imageView to .leftBottom to clip the top and right.
Darken the image for the right-side and bottom views (to make it look a little "shadowed"
Then, apply CGAffineTransform to skew the right-side and bottom views.
Using this image (3:2 ratio):
and this code (everything is done via code - no IBOutlets needed):
import UIKit
import CoreImage
class ImageWorkViewController: UIViewController {
let mainImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.clipsToBounds = true
v.contentMode = .topLeft
return v
}()
let rightImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.clipsToBounds = true
v.contentMode = .topRight
return v
}()
let bottomImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.clipsToBounds = true
v.contentMode = .bottomLeft
return v
}()
// this will be the width of the skewed right-side and height of the skewed bottom
let vDepth:CGFloat = 10.0
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mainImageView)
view.addSubview(rightImageView)
view.addSubview(bottomImageView)
NSLayoutConstraint.activate([
// constrain main image view 40-pts from each side
mainImageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40.0),
mainImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40.0),
// centered vertically
mainImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
// use 3:2 ratio
mainImageView.heightAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 2.0 / 3.0),
// constrain right image view to main image view
// right-edge
// top + 1/2 of vDepth
// equal height
// width = vDepth
rightImageView.leadingAnchor.constraint(equalTo: mainImageView.trailingAnchor, constant: 0.0),
rightImageView.topAnchor.constraint(equalTo: mainImageView.topAnchor, constant: vDepth / 2.0),
rightImageView.heightAnchor.constraint(equalTo: mainImageView.heightAnchor, multiplier: 1.0),
rightImageView.widthAnchor.constraint(equalToConstant: vDepth),
// constrain bottom image view to main image view
// left-edge + 1/2 of vDepth
// bottom
// equal width
// height = vDepth
bottomImageView.leadingAnchor.constraint(equalTo: mainImageView.leadingAnchor, constant: vDepth / 2.0),
bottomImageView.topAnchor.constraint(equalTo: mainImageView.bottomAnchor, constant: 0.0),
bottomImageView.widthAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 1.0),
bottomImageView.heightAnchor.constraint(equalToConstant: vDepth),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// run this on viewDidLayoutSubviews() so we have valid frame sizes
if let sourceImg = UIImage(named: "goal3x2") {
// resize image to width and height of main image view, plus vDepth value
let mainImg = resizeImage(image: sourceImg, newSize: CGSize(width: mainImageView.frame.width + vDepth, height: mainImageView.frame.height + vDepth))
// set the main image
mainImageView.image = mainImg
// we're going to darken the right-side and bottom images a little bit
if let currentFilter = CIFilter(name: "CIColorControls") {
let context = CIContext(options: nil)
let beginImage = CIImage(image: mainImg)
currentFilter.setValue(beginImage, forKey: kCIInputImageKey)
// darken right-image by 40%
currentFilter.setValue(-0.4, forKey: kCIInputBrightnessKey)
if let output = currentFilter.outputImage {
if let cgimg = context.createCGImage(output, from: output.extent) {
let processedImage = UIImage(cgImage: cgimg)
// set the right-side image
rightImageView.image = processedImage
}
}
// darken bottom-image by 50%
currentFilter.setValue(-0.5, forKey: kCIInputBrightnessKey)
if let output = currentFilter.outputImage {
if let cgimg = context.createCGImage(output, from: output.extent) {
let processedImage = UIImage(cgImage: cgimg)
// set the bottom image
bottomImageView.image = processedImage
}
}
}
}
// skew the right-side and bottom image views
let skewVal: CGFloat = 1.0
// bottom part transform
let bottomTransform = CGAffineTransform(a: 1.0, b: 0.0, c: skewVal, d: 1.0, tx: 0.0, ty: 0.0)
bottomImageView.transform = bottomTransform
// right part transform
let rightTransform = CGAffineTransform(a: 1.0, b: skewVal, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0)
rightImageView.transform = rightTransform
}
func resizeImage(image: UIImage, newSize: CGSize) -> UIImage {
let newWidth = newSize.width
let newHeight = newSize.height
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
image.draw(in: CGRect(x: 0.0, y: 0.0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
This is the result:
There is a variable named vDepth that controls the width of the right-side and the height of the bottom imageViews.
Note: this is just example code... Hopefully this will get you on your way.

Resources