Swift - How To Make A Table Containing Labels - ios

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

Related

Custom UITabBar irregular shape programmatically

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.

Corner radius image Swift

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

Create slanted cut on image view swift

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
}

swift Export CAShapeLayer animation to gif

I have a small code on Swift, that makes animation drawing of house. For animation drawing I use CAShapeLayer() based on UIBezierPath():
func setupDrawingLayer() {
// Stop and remove all other actions and pics on the animationLayer
clearLayer()
if let _ = animationLayer{
let pathRect: CGRect = animationLayer!.bounds.insetBy(dx: 100.0, dy: 100.0)
let bottomLeft = CGPoint(x: pathRect.minX, y: pathRect.minY)
let topLeft = CGPoint(x: pathRect.minX, y: pathRect.minY + pathRect.height * 2.0 / 3.0)
let bottomRight = CGPoint(x: pathRect.maxX, y: pathRect.minY)
let topRight = CGPoint(x: pathRect.maxX, y: pathRect.minY + pathRect.height * 2.0 / 3.0)
let roofTip = CGPoint(x: pathRect.midX, y: pathRect.maxY)
let path = UIBezierPath()
path.move(to: bottomLeft)
path.addLine(to: topLeft)
...
path.addLine(to: bottomLeft)
path.addLine(to: bottomRight)
let pathShapeLayer = CAShapeLayer()
pathShapeLayer.frame = animationLayer!.bounds
pathShapeLayer.bounds = pathRect
pathShapeLayer.isGeometryFlipped = true
pathShapeLayer.path = path.cgPath
pathShapeLayer.strokeColor = UIColor.black.cgColor
pathShapeLayer.fillColor = nil
pathShapeLayer.lineWidth = 10.0
pathShapeLayer.lineJoin = kCALineJoinBevel
animationLayer!.addSublayer(pathShapeLayer)
pathLayer = pathShapeLayer
}
}
I need to export this animation to GIF file. How can I do this?
Or may be you know some other solution, that can animate UIBezierPath() drawing with exporting to GIF?
Thank you.

Creating Triangle with UIBezierPath in Swift

I am trying to understand how to create a triangle shape with Swift. I found this code that creates a triangle.
class TriangleLayer: CAShapeLayer {
let innerPadding: CGFloat = 30.0
override init() {
super.init()
fillColor = Colors.red.CGColor
strokeColor = Colors.red.CGColor
lineWidth = 7.0
lineCap = kCALineCapRound
lineJoin = kCALineJoinRound
path = trianglePathSmall.CGPath
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var trianglePathSmall: UIBezierPath {
let trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0 + innerPadding, y: 95.0)) // #1
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5 + innerPadding)) // #2
trianglePath.addLineToPoint(CGPoint(x: 95.0 - innerPadding, y: 95.0)) // #3
trianglePath.closePath()
return trianglePath
}
And this code creates a shape like this
in the middle of the screen.
I tried to tweak and play around with it to understand how it works; however, at this point I realised that I got lost with the logic quite a bit. I placed the CGPoints of above triangle on an x-y axis in my head and it seems something like:
#1 x:35, y:95 #3 x:65, y:95
  #2 x:50, y: 42.5
But the triangle is created upside-down if I place the dots on the x-y axis.
What I want to achieve is what the axis tells, and I want to achieve..
. . .
<like this. not this>
. . .
You just have the axes in your head upside down. The coordinate system starts at 0,0 and extends right in X and down in Y.
So your points are really:
#2 x:50, y: 42.5
#1 x:35, y:95 #3 x:65, y:95
to get your desired triangle you'd have something like:
#1 x:35, y:95 #3 x:65, y:95
#2 x:50, y: 147.5
Result triangles
Code in swift5
//TriangleView
extension UIView {
func setRightTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width //you can use triangleView.frame.size.height
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:heightWidth, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setLeftTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:0, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setUpTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:heightWidth))
path.addLine(to: CGPoint(x:0, y:heightWidth))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setDownTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:0))
path.addLine(to: CGPoint(x:0, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
}
Swift 4.*
The easiest way of doing it by using AutoLayout:
Open your Storyboard and drag a UIView in UIViewController, position it and set the size as you wish (that's the place where the triangle will be). Set the view background to be transparent.
Create a new class, you can name it however you want (I named mine TriangleView). This will be the content of that class:
class TriangleView: UIView {
// predefined variables that can be changed
var startPoint: CGPoint = CGPoint(x: 0.0, y: 0.5)
var endPoint: CGPoint = CGPoint(x: 1.0, y: 0.5)
var firstGradientColor: UIColor = UIColor.white
var secondGradientColor: UIColor = UIColor.blue
let gradient: CAGradientLayer = CAGradientLayer()
override func draw(_ rect: CGRect) {
let height = self.layer.frame.size.height
let width = self.layer.frame.size.width
// draw the triangle
let path = UIBezierPath()
path.move(to: CGPoint(x: width / 2, y: 0))
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.close()
// draw the triangle 'upside down'
// let path = UIBezierPath()
// path.move(to: CGPoint(x: 0, y: 0))
// path.addLine(to: CGPoint(x: width, y: 0))
// path.addLine(to: CGPoint(x: width / 2, y: height))
// path.close()
// add path to layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = 1.0
// Add the gradient for the view background if needed
gradient.colors = [firstGradientColor.cgColor, secondGradiendColor.cgColor]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = self.bounds
gradient.mask = shapeLayer
self.layer.addSublayer(gradient)
}
}
Go to your Storyboard, select the UIView and in Identity Inspector write the class name TriangleView
Enjoy your triangle! :)

Resources