问题
I am trying to create custom parallelogram view using UIBezierPath
but not getting a perfect one.
Following is my custom view code
class CustomView: UIView {
override func draw(_ rect: CGRect) {
let offset = 60.0;
let path = UIBezierPath()
path.move(to: CGPoint(x: self.frame.origin.x + CGFloat(offset), y: self.frame.origin.y))
path.addLine(to: CGPoint(x: self.frame.width + self.frame.origin.x , y: self.frame.origin.y))
path.addLine(to: CGPoint(x: self.frame.origin.x + self.frame.width - CGFloat(offset) , y: self.frame.origin.y + self.frame.height))
path.addLine(to: CGPoint(x: self.frame.origin.x, y: self.frame.origin.y + self.frame.height))
// Close the path. This will create the last line automatically.
path.close()
UIColor.red.setFill()
path.fill()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer;
}
}
And i create the view using
let customView = CustomView()
customView.frame = CGRect(origin: CGPoint(x: 10, y: 20), size: CGSize(width: 250, height: 250))
self.view.addSubview(customView)
But i got the view like this and it is not a perfect parallelogram.
回答1:
The problem is the use of frame within draw(_:)
. The issue is that frame
is “the size and position of the view in its superview’s coordinate system” (emphasis added). How you render this shape within this view generally has nothing to do with where this view is located in its superview. And if this view doesn’t happen to be at 0, 0 of its superview, you can experience cropping.
But don’t use rect parameter, either. “The first time your view is drawn, this rectangle is typically the entire visible bounds of your view. However, during subsequent drawing operations, the rectangle may specify only part of your view.” You risk having shape radically changed if the OS decides it only needs to update a part of your CustomView
.
Use bounds, instead, which is in the view’s own coordinate system. And, using minX
, minY
, maxX
, and maxY
simplifies the code a bit.
@IBDesignable
class CustomView: UIView {
@IBInspectable var offset: CGFloat = 60 { didSet { setNeedsDisplay() } }
@IBInspectable var fillColor: UIColor = .red { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.minX + offset, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX - offset, y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
// Close the path. This will create the last line automatically.
path.close()
fillColor.setFill()
path.fill()
}
}
As an aside, I’m not setting the mask. If you’re animating, it seems that it might be inefficient to constantly reset the mask every time draw
is called. I’d personally just set the background color of the view to .clear
. But that’s not relevant to the immediate question.
回答2:
As @Losiowaty suggests, the math is off in that the upper right corner is extending beyond the bounds of the frame. (Not sure why clipToBounds=false doesn't work; but that's just a debug suggestion - not a solution). Try this:
class CustomView: UIView {
override func draw(_ rect: CGRect) {
let offset: CGFloat = 60.0;
let path = UIBezierPath()
let width = self.bounds.width - offset
let upperLeftPoint = CGPoint(x: self.bounds.origin.x + offset, y: self.bounds.origin.y)
let upperRightPoint = CGPoint(x: self.bounds.origin.x + width, y: self.bounds.origin.y)
let lowerRightPoint = CGPoint(x: width - offset, y: self.bounds.size.height)
let lowerLeftPoint = CGPoint(x: self.bounds.origin.x, y: self.bounds.size.height)
path.move(to: upperLeftPoint)
path.addLine(to: upperRightPoint)
path.addLine(to: lowerRightPoint)
path.addLine(to: lowerLeftPoint)
path.addLine(to: upperLeftPoint)
// Close the path. This will create the last line automatically.
path.close()
UIColor.red.setFill()
path.fill()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer;
}
}
来源:https://stackoverflow.com/questions/54496532/parallelogram-view-using-uibezierpath