I have been looking around the internet and I can't find a good solution for making a slanted cut on an image view that works for swift.
Here is what I want
As you can see I would like to slant an image view as seen in the background. If anyone had some thoughts or solutions, that would be much appreciated.
Properties:
fileprivate var headerView: PostHeaderView!
fileprivate var headerMaskLayer: CAShapeLayer!
In viewDidLoad():
headerMaskLayer = CAShapeLayer()
headerMaskLayer.fillColor = UIColor.black.cgColor
headerView.layer.mask = headerMaskLayer
updateHeaderView()
Then use this function:
func updateHeaderView() {
let effectiveHeight = Storyboard.tableHeaderHeight - Storyboard.tableHeaderCutAway / 2
var headerRect = CGRect(x: 0, y: -effectiveHeight, width: tableView.bounds.width, height: Storyboard.tableHeaderHeight)
headerView.logoImageView.alpha = 0
if tableView.contentOffset.y < -effectiveHeight {
headerRect.origin.y = tableView.contentOffset.y
headerRect.size.height = -tableView.contentOffset.y + Storyboard.tableHeaderCutAway/2
let final: CGFloat = -100
let alpha = min((tableView.contentOffset.y + effectiveHeight) / final, 1)
headerView.logoImageView.alpha = alpha
}
headerView.frame = headerRect
// cut away
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: headerRect.width, y: 0))
path.addLine(to: CGPoint(x: headerRect.width, y: headerRect.height))
path.addLine(to: CGPoint(x: 0, y: headerRect.height - Storyboard.tableHeaderCutAway))
headerMaskLayer?.path = path.cgPath
}
Related
I need to create custom tabBar irregular shape programmatically. I found a lot of decisions, but they all are connected to Interface Builder. The code is below. All the methods of customized tabBar don't call while debugging.
final class TabBar: UITabBarController {
var customTabBar = CustomizedTabBar()
override var tabBar: UITabBar {
return customTabBar
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .naviBarBlack
UITabBar.appearance().barTintColor = .naviBarBlack
UITabBar.appearance().clipsToBounds = false
tabBar.tintColor = .white
tabBar.itemPositioning = .centered
setupVCs()
}
func setupVCs() {
guard let homeUnselected = UIImage(named: "home-unselected"),
let homeSelected = UIImage(named: "home-selected"),
let likeUnselected = UIImage(named: "like-unselected"),
let likeSelected = UIImage(named:"like-selected") else {return}
self.viewControllers = [
createNavController(for: MainScreenViewController(),
image: homeUnselected,
selected: homeSelected),
createNavController(for: UIViewController(),
image: likeUnselected,
selected: likeSelected)
]
}
private func createNavController(for rootViewController: UIViewController,
image: UIImage,
selected: UIImage) -> UIViewController {
let navController = UINavigationController(rootViewController: rootViewController)
navController.tabBarItem.image = image
navController.tabBarItem.selectedImage = selected
return navController
}
}
class CustomizedTabBar: UITabBar {
private var shapeLayer: CALayer?
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
override func draw(_ rect: CGRect) {
self.addShape()
}
func createPath() -> CGPath {
let height: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0)) // start top left
path.addLine(to: CGPoint(x: (centerWidth - height * 2), y: 0)) // the beginning of the trough
// first curve down
path.addCurve(to: CGPoint(x: centerWidth, y: height),
controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
// second curve up
path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
// complete the rect
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let buttonRadius: CGFloat = 35
return abs(self.center.x - point.x) > buttonRadius || abs(point.y) > buttonRadius
}
func createPathCircle() -> CGPath {
let radius: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: (centerWidth - radius * 2), y: 0))
path.addArc(withCenter: CGPoint(x: centerWidth, y: 0), radius: radius, startAngle: CGFloat(180).degreesToRadians, endAngle: CGFloat(0).degreesToRadians, clockwise: false)
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
}
extension CGFloat {
var degreesToRadians: CGFloat { return self * .pi / 180 }
var radiansToDegrees: CGFloat { return self * 180 / .pi }
}
So this's what i want to see (from https://betterprogramming.pub/draw-a-custom-ios-tabbar-shape-27d298a7f4fa)
And what i get.
While the Apple docs for UITabBarController state:
You should never attempt to manipulate the UITabBar object itself stored in this property.
you can find many, many examples of custom tab bars out there.
For your specific approach, don't try overriding var tabBar:
Instead, if you have your TabBarController in Storyboard, assign the custom class of its TabBar to CustomizedTabBar.
Or, if you're instantiating the Controller from code, you could try this:
override func viewDidLoad() {
super.viewDidLoad()
let tabBar = { () -> CustomizedTabBar in
let tabBar = CustomizedTabBar()
tabBar.delegate = self
return tabBar
}()
self.setValue(tabBar, forKey: "tabBar")
// ... the rest of your viewDidLoad()
}
I'd recommend reading through several other examples though, and look for a common (reliable) approach.
I need to change color of bottom cell in UICollectionView, in this question just I do this
I need set color to bottom cell like this
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor(red: 184/255, green: 215/255, blue: 215/255, alpha: 1).cgColor
border.frame = CGRect(x: 0, y: cell.frame.size.height - width, width: cell.frame.size.width, height: cell.frame.size.height)
border.borderWidth = width
cell.layer.addSublayer(border)
cell.layer.masksToBounds = true
Instead of trying to add it as a border, I would add two layers instead as it is much easier. Something like :
override func layoutSubviews() {
super.layoutSubviews()
backgroundShape = CAShapeLayer.init()
backPath = UIBezierPath.init(rect: self.bounds)// Use your path
backgroundShape.path = backPath.cgPath
backgroundShape.fillColor = UIColor.blue.cgColor//border color in your case
self.layer.addSublayer(backgroundShape)
foregroundShape = CAShapeLayer()
forgroundPath = UIBezierPath.init(rect: CGRect.init(x: 0, y: -20, width: self.bounds.width, height: self.bounds.height))// Use your path with a little negative y
foregroundShape.path = forgroundPath.cgPath
foregroundShape.fillColor = UIColor.yellow.cgColor//white in your case
self.layer.addSublayer(foregroundShape)
}
A detailed answer:
class BorderedCell: UICollectionViewCell{
var backgroundShape: CAShapeLayer!
var backPath: UIBezierPath!
var foregroundShape: CAShapeLayer!
var forgroundPath: UIBezierPath!
override func layoutSubviews() {
super.layoutSubviews()
backgroundShape = CAShapeLayer.init()
backPath = drawCurvedShape(with: 0)
backgroundShape.path = backPath.cgPath
backgroundShape.fillColor = UIColor(red:0.76, green:0.86, blue:0.86, alpha:1.0).cgColor
self.layer.addSublayer(backgroundShape)
foregroundShape = CAShapeLayer()
forgroundPath = drawCurvedShape(with: -8)
foregroundShape.path = forgroundPath.cgPath
foregroundShape.fillColor = UIColor.white.cgColor
self.layer.addSublayer(foregroundShape)
}
func drawCurvedShape(with startingY: CGFloat) -> UIBezierPath{
let path = UIBezierPath.init()
path.move(to: CGPoint.init(x: 0, y: startingY))
path.addLine(to: CGPoint.init(x: self.bounds.width, y: startingY))
path.addLine(to: CGPoint.init(x: self.bounds.width, y: self.bounds.height + startingY - 30))
path.addQuadCurve(to: CGPoint.init(x: self.bounds.width - 30, y: self.bounds.height + startingY), controlPoint: CGPoint.init(x: self.bounds.width, y: self.bounds.height + startingY))
path.addLine(to: CGPoint.init(x: 0, y: self.bounds.height + startingY))
path.addLine(to: CGPoint.init(x: 0, y: startingY))
path.close()
return path
}
}
Output:
I did not make the drawShape exactly to the shape you require, just to something that would serve the purpose.
I'm not totally sure to understand how UIBezierPath is supposed to work.
I have added a simple UIView in the middle of the screen, and I wanted to clip it by adding a mask to its layer. I tried this, thinking I'd get something like a losange in the middle of the view:
override func viewDidLoad() {
super.viewDidLoad()
viewToClip.backgroundColor = .white
let bezierPath = UIBezierPath()
bezierPath.move(to: viewToClip.center)
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x - 5, y: viewToClip.center.y))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x, y: viewToClip.center.y - 5))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x + 5, y: viewToClip.center.y))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x, y: viewToClip.center.y + 5))
bezierPath.close()
let testLayer = CAShapeLayer()
testLayer.path = bezierPath.cgPath
viewToClip.layer.mask = testLayer
}
But instead of that, the view simply disappears from the screen. What am I doing wrong?
Thanks for your help.
Can you try
import UIKit
class bb: UIView {
var once:Bool = true
override func draw(_ rect: CGRect) {
if(once)
{
once = false
self.backgroundColor = .white
let bezierPath = UIBezierPath()
let cen = CGPoint.init(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
bezierPath.move(to: cen)
bezierPath.addLine(to: CGPoint(x: cen.x - 5, y: cen.y))
bezierPath.addLine(to: CGPoint(x: cen.x, y: cen.y - 5))
bezierPath.addLine(to: CGPoint(x: cen.x + 5, y: cen.y))
bezierPath.addLine(to: CGPoint(x: cen.x, y: cen.y + 5))
bezierPath.close()
let testLayer = CAShapeLayer()
testLayer.path = bezierPath.cgPath
testLayer.lineWidth = 1.0
testLayer.strokeColor = UIColor.blue.cgColor
testLayer.fillColor = UIColor.green.cgColor
self.layer.addSublayer(testLayer)
}
}
This code works when the frame's x and y are 0, but fails when using different x and y's:
class Triangle: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
let startX = self.center.x
let startY: CGFloat = 0
path.move(to: CGPoint(x: startX, y: startY))
path.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height))
path.addLine(to: CGPoint(x: 0, y: self.bounds.height))
path.close()
UIColor.green.setStroke()
path.stroke()
}
}
import UIKit
class ViewController: UIViewController {
#IBAction func animate(_ sender: UIButton) {
let triangleView = Triangle(frame: CGRect(x: 50, y: 50, width: 30, height: 30))
triangleView.backgroundColor = .clear
self.view.addSubview(triangleView)
}
}
This works:
let triangleView = Triangle(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
When failing it looks like this:
Well that is one ugly triangle. Why does it works with x: 0 and y:0 and fails when using different floats there? How can I fix this?
replace let startX = self.center.x with let startX = self. bounds.width / 2
This is what I want to achieve.
I tried coding it myself but the first outer view border does not show up.
Here's my code:
import UIKit
class InfoTableView: UIView {
override func drawRect(rect: CGRect) {
self.backgroundColor = UIColor.whiteColor()
let outerBorder = UIColorCode.init(hexString: "#666666")
let startingTopPoint = CGPoint(x: rect.minX, y: rect.minY)
let endingTopPoint = CGPoint(x: rect.maxX, y: rect.minY)
let startingPoint = CGPoint(x: rect.minX, y: rect.maxY)
let endingPoint = CGPoint(x: rect.maxX, y: rect.maxY)
// top
let tpPath = UIBezierPath()
tpPath.moveToPoint(startingPoint)
tpPath.addLineToPoint(endingTopPoint)
tpPath.lineWidth = 2.0
outerBorder.setStroke()
tpPath.stroke()
// bottom
let btPath = UIBezierPath()
btPath.moveToPoint(startingPoint)
btPath.addLineToPoint(endingPoint)
btPath.lineWidth = 2.0
outerBorder.setStroke()
btPath.stroke()
}
}
There are outer borders top and bottom. But only the bottom one shows up. I don't know where did I go wrong.
I have made little bit of edits on your code . Try if it works for you.
override func drawRect(rect: CGRect) {
// Drawing code
self.backgroundColor = UIColor.whiteColor()
let outerBorder = UIColor.redColor()
let lineWidth : CGFloat = 2.0
let insetRect = rect.insetBy(dx: lineWidth/2, dy: lineWidth/2)
let startingTopPoint = CGPointMake(insetRect.origin.x,insetRect.origin.y)
let endingTopPoint = CGPoint(x: insetRect.maxX, y: insetRect.minY)
let startingPoint = CGPoint(x: insetRect.minX, y: insetRect.maxY)
let endingPoint = CGPoint(x: insetRect.maxX, y: insetRect.maxY)
// top
let tpPath = UIBezierPath()
tpPath.moveToPoint(startingTopPoint)
tpPath.addLineToPoint(endingTopPoint)
tpPath.lineWidth = 2.0
outerBorder.setStroke()
tpPath.stroke()
// bottom
let btPath = UIBezierPath()
btPath.moveToPoint(startingPoint)
btPath.addLineToPoint(endingPoint)
btPath.lineWidth = 2.0
outerBorder.setStroke()
btPath.stroke()
}