Original image is like this Hai i am new to iOS my requirement is display images in diamond shape so for that purpose i followed the following code. I took a UIView and ImageView is subview to that UIView. i am getting diamond shape but image is flipping . how to solve that issue?
var tr: CGAffineTransform = CGAffineTransformIdentity
tr = CGAffineTransformScale(tr, 0.8, 1)
tr = CGAffineTransformRotate(tr, 0.7)
self.views.transform = tr
You can use UIBezierPath to draw shapes
import UIKit
extension UIView
{
func addDiamondMaskToView()
{
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: self.bounds.size.width / 2.0, y: 0))
path.addLineToPoint(CGPoint(x: self.bounds.size.width, y: self.bounds.size.height / 2.0))
path.addLineToPoint(CGPoint(x: self.bounds.size.width / 2.0, y: self.bounds.size.height))
path.addLineToPoint(CGPoint(x: 0, y: self.bounds.size.height / 2.0))
path.closePath()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.fillColor = UIColor.whiteColor().CGColor
shapeLayer.strokeColor = UIColor.clearColor().CGColor
self.layer.mask = shapeLayer
}
}
You can call the method as
//suppose this is your imageview
let imgView = UIImageView(frame: CGRectMake(0, 0, 100, 200))
//then call the method as
imgView.addDiamondMaskToView()
Its working fine for me, check the images for reference
As you have provided the image, after analysing I can conclude that you need to do is
Add diagonal mask to the ImageView
Rotate image inside that imageView to some angle
assuming that the image is rotated to 45 degrees, I'm providing the solution as
import UIKit
extension UIView
{
func addDiamondMaskToView()
{
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: self.bounds.size.width / 2.0, y: 0))
path.addLineToPoint(CGPoint(x: self.bounds.size.width, y: self.bounds.size.height / 2.0))
path.addLineToPoint(CGPoint(x: self.bounds.size.width / 2.0, y: self.bounds.size.height))
path.addLineToPoint(CGPoint(x: 0, y: self.bounds.size.height / 2.0))
path.closePath()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.fillColor = UIColor.whiteColor().CGColor
shapeLayer.strokeColor = UIColor.clearColor().CGColor
self.layer.mask = shapeLayer
}
}
extension UIImageView
{
func addDiamondWithSomeAngle(angleInDegrees degrees : CGFloat)
{
self.addDiamondMaskToView()
self.image = self.image?.imageRotatedByDegrees(degrees, flip: false)
}
}
extension UIImage {
public func imageRotatedByDegrees(degrees: CGFloat, flip: Bool) -> UIImage
{
let degreesToRadians: (CGFloat) -> CGFloat = {
return $0 / 180.0 * CGFloat(M_PI)
}
let rotatedViewBox = UIView(frame: CGRect(origin: CGPointZero, size: size))
let t = CGAffineTransformMakeRotation(degreesToRadians(degrees));
rotatedViewBox.transform = t
let rotatedSize = rotatedViewBox.frame.size
UIGraphicsBeginImageContext(rotatedSize)
let bitmap = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(bitmap, rotatedSize.width / 2.0, rotatedSize.height / 2.0);
CGContextRotateCTM(bitmap, degreesToRadians(degrees));
var yFlip: CGFloat
if(flip){
yFlip = CGFloat(-1.0)
} else {
yFlip = CGFloat(1.0)
}
CGContextScaleCTM(bitmap, yFlip, -1.0)
CGContextDrawImage(bitmap, CGRectMake(-size.width / 2, -size.height / 2, size.width, size.height), CGImage)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
You need to call the method as
imageView.addDiamondWithSomeAngle(angleInDegrees: 45)
Output is as follows
Courtesy : Image rotation by confile
Related
I'm trying to make this corner radius image...it's not exactly the same shape of the image..any easy answer instead of trying random numbers of width and height ?
thanks alot
let rectShape = CAShapeLayer()
rectShape.bounds = self.mainImg.frame
rectShape.position = self.mainImg.center
rectShape.path = UIBezierPath(roundedRect: self.mainImg.bounds, byRoundingCorners: [.bottomLeft , .bottomRight ], cornerRadii: CGSize(width: 50, height: 4)).cgPath
You can use QuadCurve to get the design you want.
Here is a Swift #IBDesignable class that lets you specify the image and the "height" of the rounding in Storyboard / Interface Builder:
#IBDesignable
class RoundedBottomImageView: UIView {
var imageView: UIImageView!
#IBInspectable var image: UIImage? {
didSet { self.imageView.image = image }
}
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
doMyInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
doMyInit()
}
func doMyInit() {
imageView = UIImageView()
imageView.backgroundColor = UIColor.red
imageView.contentMode = UIViewContentMode.scaleAspectFill
addSubview(imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = self.bounds
let rect = self.bounds
let y:CGFloat = rect.size.height - roundingValue
let curveTo:CGFloat = rect.size.height + roundingValue
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: curveTo))
myBezier.addLine(to: CGPoint(x: rect.width, y: 0))
myBezier.addLine(to: CGPoint(x: 0, y: 0))
myBezier.close()
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
Result with 300 x 200 image view, rounding set to 40:
Edit - (3.5 years later)...
To answer #MiteshDobareeya comment, we can switch the rounded edge from Bottom to Top by transforming the bezier path:
let c = CGAffineTransform(scaleX: 1, y: -1).concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
myBezier.apply(c)
It's been quite a while since this answer was originally posted, so a few changes:
subclass UIImageView directly - no need to make it a UIView with an embedded UIImageView
add a Bool roundTop var
if set to False (the default), we round the Bottom
if set to True, we round the Top
re-order and "name" our path points for clarity
So, the basic principle:
We create a UIBezierPath and:
move to pt1
add a line to pt2
add a line to pt3
add a quad-curve to pt4 with controlPoint
close the path
use that path for a CAShapeLayer mask
the result:
If we want to round the Top, after closing the path we can apply apply a scale transform using -1 as the y value to vertically mirror it. Because that transform mirror it at "y-zero" we also apply a translate transform to move it back down into place.
That gives us:
Here's the updated class:
#IBDesignable
class RoundedTopBottomImageView: UIImageView {
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
#IBInspectable var roundTop: Bool = false {
didSet {
self.setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
let r = bounds
let myBezier = UIBezierPath()
let pt1: CGPoint = CGPoint(x: r.minX, y: r.minY)
let pt2: CGPoint = CGPoint(x: r.maxX, y: r.minY)
let pt3: CGPoint = CGPoint(x: r.maxX, y: r.maxY - roundingValue)
let pt4: CGPoint = CGPoint(x: r.minX, y: r.maxY - roundingValue)
let controlPoint: CGPoint = CGPoint(x: r.midX, y: r.maxY + roundingValue)
myBezier.move(to: pt1)
myBezier.addLine(to: pt2)
myBezier.addLine(to: pt3)
myBezier.addQuadCurve(to: pt4, controlPoint: controlPoint)
myBezier.close()
if roundTop {
// if we want to round the Top instead of the bottom,
// flip the path vertically
let c = CGAffineTransform(scaleX: 1, y: -1) //.concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
myBezier.apply(c)
}
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
You can try with UIView extension. as
extension UIView {
func setBottomCurve(){
let offset = CGFloat(self.frame.size.height + self.frame.size.height/1.8)
let bounds = self.bounds
let rectBounds = CGRect(x: bounds.origin.x,
y: bounds.origin.y ,
width: bounds.size.width,
height: bounds.size.height / 2)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalBounds = CGRect(x: bounds.origin.x - offset / 2,
y: bounds.origin.y ,
width: bounds.size.width + offset,
height: bounds.size.height)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
& use it in viewWillAppear like methods where you can get actual frame of UIImageView.
Usage:
override func viewWillAppear(_ animated: Bool) {
//use it in viewWillAppear like methods where you can get actual frame of UIImageView
myImageView.setBottomCurve()
}
I Need to add three colour in single background colour
Without using 3 UIView or image.
Create custom UIView and override the draw(_:) function. Then use the current CGContext and draw according to your preferred size. Example based on the alignment from the given image is shown below:
class CustomView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
let firstRect = CGRect(origin: CGPoint(x: rect.origin.x, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let middleRect = CGRect(origin: CGPoint(x: firstRect.maxX, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let lastRect = CGRect(origin: CGPoint(x: middleRect.maxX, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let arrayTuple: [(rect: CGRect, color: CGColor)] = [(firstRect, UIColor.red.cgColor), (middleRect, UIColor.green.cgColor), (lastRect, UIColor.blue.cgColor)]
for tuple in arrayTuple {
context.setFillColor(tuple.color)
context.fill(tuple.rect)
}
}
}
Use this below func
func addSublayers (_ viewCustom : UIView){
let layer1 = CAShapeLayer()
let layer2 = CAShapeLayer()
let layer3 = CAShapeLayer()
layer1.frame = CGRect(origin: viewCustom.bounds.origin,
size: CGSize(width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height))
layer2.frame = CGRect(x: layer1.frame.size.width,
y: layer1.frame.origin.y,
width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height)
layer3.frame = CGRect(x: layer2.frame.size.width + layer2.frame.origin.x,
y: layer2.frame.origin.y,
width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height)
layer1.backgroundColor = UIColor.red.cgColor
layer2.backgroundColor = UIColor.green.cgColor
layer3.backgroundColor = UIColor.blue.cgColor
viewCustom.layer.addSublayer(layer1)
viewCustom.layer.addSublayer(layer2)
viewCustom.layer.addSublayer(layer3)
}
Output:
extension UIView {
func addMultipleColorsHorizontal(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
var colorsArray: [CGColor] = []
var locationsArray: [NSNumber] = []
for (index, color) in colors.enumerated() {
colorsArray.append(color.cgColor)
colorsArray.append(color.cgColor)
locationsArray.append(NSNumber(value: (1.0 / Double(colors.count)) * Double(index)))
locationsArray.append(NSNumber(value: (1.0 / Double(colors.count)) * Double(index + 1)))
}
gradientLayer.colors = colorsArray
gradientLayer.locations = locationsArray
gradientLayer.startPoint = CGPoint(x: 0, y: 1)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
self.backgroundColor = .clear
self.layer.addSublayer(gradientLayer)
}
}
Any idea on how to fill all the paths in here. What is currently happening is that I draw a rectangle path on my view then add small circles in between but it seems that if the circle and rectangle intersects, the white fill color is showing. What I would want is show still the gradient layer. Any help? My current code is below.
func addGradientLayer() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
let startColor = UIColor.create(withHexOrName: OurPayStatesViewUX.GradientColorStart)
let endColor = UIColor.create(withHexOrName: OurPayStatesViewUX.GradientColorEnd)
gradientLayer.colors = [startColor.CGColor, endColor.CGColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
layer.insertSublayer(gradientLayer, atIndex: 0)
}
func createOverlay(view: UIView, circleLocations: [CGPoint]) {
maskLayer?.removeFromSuperlayer()
let radius: CGFloat = view.frame.height/2
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height), cornerRadius: 0)
for i in circleLocations {
// Create a circle path in each of the state views
let circlePath = UIBezierPath(roundedRect: CGRect(x: i.x, y: i.y, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.appendPath(circlePath)
}
let rect = createRectangle(startPointX: 0, endPointX: view.bounds.size.width)
path.appendPath(rect)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.CGPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = backgroundColor?.CGColor ?? UIColor.whiteColor().CGColor
fillLayer.opacity = 1
maskLayer = fillLayer
layer.addSublayer(fillLayer)
}
func createRectangle(startPointX startPointX: CGFloat, endPointX: CGFloat) -> UIBezierPath {
let rectHeight: CGFloat = 6
let path = UIBezierPath(rect: CGRect(x: startPointX, y: frame.height/2 - rectHeight/2, width: endPointX - startPointX, height: rectHeight))
return path
}
i'm new to iOS and my goal is to add custom overlay in map view using Swift 3 and MapKit. I've followed this Add inverted circle overlay to map view.
Here is the code:
import UIKit
import MapKit
class MyMapOverlayRenderer: MKOverlayRenderer {
let diameter: Double
let fillColor: UIColor
init(overlay: MKOverlay, diameter: Double, fillColor: UIColor) {
self.diameter = diameter
self.fillColor = fillColor
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height))
path.usesEvenOddFillRule = true
let radiusInMapPoints = diameter * MKMapPointsPerMeterAtLatitude(self.overlay.coordinate.latitude)
let radiusSquared = MKMapSize(width: radiusInMapPoints, height: radiusInMapPoints)
let regionOrigin = MKMapPointForCoordinate(self.overlay.coordinate)
var regionRect = MKMapRect(origin: regionOrigin, size: radiusSquared)
regionRect = MKMapRectOffset(regionRect, -radiusInMapPoints / 2, -radiusInMapPoints / 2)
regionRect = MKMapRectIntersection(regionRect, MKMapRectWorld)
let cornerRadius = CGFloat(regionRect.size.width / Double(2))
let excludePath = UIBezierPath(roundedRect: CGRect(x: regionRect.origin.x, y: regionRect.origin.y, width: regionRect.size.width, height: regionRect.size.height), cornerRadius: cornerRadius)
path.append(excludePath)
context.setFillColor(fillColor.cgColor)
context.addPath(path.cgPath)
context.fillPath()
}
}
Eventually the overlay is shown without exclude path (a circle), any suggestions?
Solved it, just added reversing():
path.append(excludePath.reversing())
Full function code:
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height))
path.usesEvenOddFillRule = true
let radiusInMapPoints = diameter * MKMapPointsPerMeterAtLatitude(MKMapPointsPerMeterAtLatitude(overlay.coordinate.latitude))
let radiusSquared = MKMapSize(width: radiusInMapPoints, height: radiusInMapPoints)
let regionOrigin = MKMapPointForCoordinate(overlay.coordinate)
var regionRect = MKMapRect(origin: regionOrigin, size: radiusSquared)
regionRect = MKMapRectOffset(regionRect, -radiusInMapPoints / 2, -radiusInMapPoints / 2)
regionRect = MKMapRectIntersection(regionRect, MKMapRectWorld)
let midX = ( regionOrigin.x + regionRect.origin.x) / 2
let midY = ( regionOrigin.y + regionRect.origin.y) / 2
let cornerRadius = CGFloat(regionRect.size.width / Double(2))
let excludePath = UIBezierPath(roundedRect: CGRect(x: midX, y: midY, width: regionRect.size.width / 2, height: regionRect.size.height / 2), cornerRadius: cornerRadius)
path.append(excludePath.reversing())
context.setFillColor(fillColor.cgColor)
context.addPath(path.cgPath)
context.fillPath()
}
I want to create a view that looks like this:
I figure what I need is a uiview with some sort of mask, I can make a mask in the shape of a circle using a UIBezierpath, however I cannot invert this makes so that it masks everything but the circle. I need this to be a mask of a view and not a fill layer because the view that I intend to mask has a UIBlurEffect on it. The end goal is to animate this UIView overtop of my existing views to provide instruction.
Please note that I am using swift. Is there away to do this? If so, how?
Updated again for Swift 4 & removed a few items to make the code tighter.
Please note that maskLayer.fillRule is set differently between Swift 4 and Swift 4.2.
func createOverlay(frame: CGRect,
xOffset: CGFloat,
yOffset: CGFloat,
radius: CGFloat) -> UIView {
// Step 1
let overlayView = UIView(frame: frame)
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
// Step 2
let path = CGMutablePath()
path.addArc(center: CGPoint(x: xOffset, y: yOffset),
radius: radius,
startAngle: 0.0,
endAngle: 2.0 * .pi,
clockwise: false)
path.addRect(CGRect(origin: .zero, size: overlayView.frame.size))
// Step 3
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.path = path
// For Swift 4.0
maskLayer.fillRule = kCAFillRuleEvenOdd
// For Swift 4.2
maskLayer.fillRule = .evenOdd
// Step 4
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
return overlayView
}
A rough breakdown on what is happening:
Create a view sized to the specified frame, with a black background set to 60% opacity
Create the path for drawing the circle using the provided starting point and radius
Create the mask for the area to remove
Apply the mask & clip to bounds
The following code snippet will call this and place a circle in the middle of the screen with radius of 50:
let overlay = createOverlay(frame: view.frame,
xOffset: view.frame.midX,
yOffset: view.frame.midY,
radius: 50.0)
view.addSubview(overlay)
Which looks like this:
You can use this function to create what you need.
func createOverlay(frame : CGRect)
{
let overlayView = UIView(frame: frame)
overlayView.alpha = 0.6
overlayView.backgroundColor = UIColor.blackColor()
self.view.addSubview(overlayView)
let maskLayer = CAShapeLayer()
// Create a path with the rectangle in it.
var path = CGPathCreateMutable()
let radius : CGFloat = 50.0
let xOffset : CGFloat = 10
let yOffset : CGFloat = 10
CGPathAddArc(path, nil, overlayView.frame.width - radius/2 - xOffset, yOffset, radius, 0.0, 2 * 3.14, false)
CGPathAddRect(path, nil, CGRectMake(0, 0, overlayView.frame.width, overlayView.frame.height))
maskLayer.backgroundColor = UIColor.blackColor().CGColor
maskLayer.path = path;
maskLayer.fillRule = kCAFillRuleEvenOdd
// Release the path since it's not covered by ARC.
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
}
Adjust the radius and xOffset and yOffset to change the radius and position of the circle.
For Swift 3, here is rakeshbs' answer formatted so it returns the UIView needed:
func createOverlay(frame : CGRect, xOffset: CGFloat, yOffset: CGFloat, radius: CGFloat) -> UIView
{
let overlayView = UIView(frame: frame)
overlayView.alpha = 0.6
overlayView.backgroundColor = UIColor.black
// Create a path with the rectangle in it.
let path = CGMutablePath()
path.addArc(center: CGPoint(x: xOffset, y: yOffset), radius: radius, startAngle: 0.0, endAngle: 2 * 3.14, clockwise: false)
path.addRect(CGRect(x: 0, y: 0, width: overlayView.frame.width, height: overlayView.frame.height))
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.path = path;
maskLayer.fillRule = kCAFillRuleEvenOdd
// Release the path since it's not covered by ARC.
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
return overlayView
}
The above solution works great.
Say if you are looking for mask with rectangle area here is the snippet below
let fWidth = self.frame.size.width
let fHeight = self.frame.size.height
let squareWidth = fWidth/2
let topLeft = CGPoint(x: fWidth/2-squareWidth/2, y: fHeight/2-squareWidth/2)
let topRight = CGPoint(x: fWidth/2+squareWidth/2, y: fHeight/2-squareWidth/2)
let bottomLeft = CGPoint(x: fWidth/2-squareWidth/2, y: fHeight/2+squareWidth/2)
let bottomRight = CGPoint(x: fWidth/2+squareWidth/2, y: fHeight/2+squareWidth/2)
let cornerWidth = squareWidth/4
// Step 2
let path = CGMutablePath()
path.addRoundedRect(in: CGRect(x: topLeft.x, y: topLeft.y,
width: topRight.x - topLeft.x, height: bottomLeft.y - topLeft.y),
cornerWidth: 20, cornerHeight: 20)
path.addRect(CGRect(origin: .zero, size: self.frame.size))
// Step 3
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.path = path
// For Swift 4.0
maskLayer.fillRule = kCAFillRuleEvenOdd
// For Swift 4.2
//maskLayer.fillRule = .evenOdd
// Step 4
self.layer.mask = maskLayer
self.clipsToBounds = true
rectangle mask looks like this