Swift 4 view with curved center - ios

i have difficulties to create UI. I tried to create custom view, but i can't see even the background of this view. Here is my sample code, i took it from another post, but i don't know how to change it to my case.
I do use SnapKit for my UI elements.
My View:
lazy var greenView: CurvedView = {
let view = CurvedView()
view.backgroundColor = #colorLiteral(red: 0.08778516203, green: 0.7643524408, blue: 0.1997725368, alpha: 1)
DispatchQueue.main.async {
view.snp.makeConstraints{ (make) -> Void in
make.top.equalTo(self.shipView.snp.bottom).offset(100)
make.left.right.equalTo(self.scrollContentView)
make.height.equalTo(200)
}
}
return view
}()
Tried to create custom View:
class CurvedView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
StyleKitName.drawCanvas1(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 400), resizing: .aspectFit)
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
UPDATED green customView with PaintCode
public class StyleKitName : UIView {
//// Drawing Methods
#objc dynamic public class func drawCanvas1(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 240, height: 120), resizing: ResizingBehavior = .aspectFit) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 240, height: 120), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 240, y: resizedFrame.height / 120)
//// Color Declarations
let color = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
let color2 = #colorLiteral(red: 0.7450980544, green: 0.1568627506, blue: 0.07450980693, alpha: 1)
//// Rectangle Drawing
let rectanglePath = UIBezierPath()
rectanglePath.move(to: CGPoint(x: 48, y: 103))
rectanglePath.addLine(to: CGPoint(x: 191, y: 103))
rectanglePath.addLine(to: CGPoint(x: 191, y: 9))
rectanglePath.addLine(to: CGPoint(x: 48, y: 9))
rectanglePath.addLine(to: CGPoint(x: 48, y: 103))
rectanglePath.close()
color.setFill()
rectanglePath.fill()
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 96, y: 9))
bezierPath.addCurve(to: CGPoint(x: 110, y: 15), controlPoint1: CGPoint(x: 104.11, y: 9.42), controlPoint2: CGPoint(x: 107, y: 12))
bezierPath.addCurve(to: CGPoint(x: 123, y: 23), controlPoint1: CGPoint(x: 114.16, y: 19.16), controlPoint2: CGPoint(x: 115.63, y: 23))
bezierPath.addCurve(to: CGPoint(x: 136, y: 15), controlPoint1: CGPoint(x: 129.95, y: 23), controlPoint2: CGPoint(x: 132, y: 19))
bezierPath.addCurve(to: CGPoint(x: 152, y: 9), controlPoint1: CGPoint(x: 139.3, y: 11.7), controlPoint2: CGPoint(x: 145.35, y: 9))
bezierPath.addCurve(to: CGPoint(x: 96, y: 9), controlPoint1: CGPoint(x: 166.7, y: 9), controlPoint2: CGPoint(x: 96, y: 9))
bezierPath.addLine(to: CGPoint(x: 148.61, y: 9))
color2.setFill()
bezierPath.fill()
context.restoreGState()
}
#objc(StyleKitNameResizingBehavior)
public enum ResizingBehavior: Int {
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
case stretch /// The content is stretched to match the entire target rectangle.
case center /// The content is centered in the target rectangle, but it is NOT resized.
public func apply(rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .aspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .aspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .stretch:
break
case .center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
}
https://d.radikal.ru/d30/1902/c5/c07cac5532d0.png

This is the basic way to use that PaintCode generated code...
class StyleView: UIView {
override func draw(_ rect: CGRect) {
StyleKitName.drawCanvas1()
}
}
class StyleTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = StyleView()
v.frame = CGRect(x: 0, y: 0, width: 240, height: 120)
v.center = view.center
v.backgroundColor = .blue
view.addSubview(v)
}
}
Result:

Related

Swift Tabbar with a custom shape in the middle

i want to make this shape in swift .
As you can see, the tabbar has a raised center button. However, this is not the only thing as there should be a real hole in the tabbar so that it is transparent there.
How can I create such a hole inside a tabbar? And then put a raised, round button in that hole?
I would gladly appreciate any help regarding my question.
i am trying but cannot achieve the above result.
import Foundation
import UIKit
#IBDesignable
class AppTabBar: UITabBar {
private var shapeLayer: CALayer?
override func draw(_ rect: CGRect) {
self.addShape()
}
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.fillColor = #colorLiteral(red: 0.9782002568, green: 0.9782230258, blue: 0.9782107472, alpha: 1)
shapeLayer.lineWidth = 0.5
// The below 4 lines are for shadow above the bar. you can skip them if you do not want a shadow
shapeLayer.shadowOffset = CGSize(width:0, height:0)
shapeLayer.shadowRadius = 10
shapeLayer.shadowColor = UIColor.gray.cgColor
shapeLayer.shadowOpacity = 0.3
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
func createPath() -> CGPath {
let height2: CGFloat = self.frame.height
let height: CGFloat = 86.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
let startXpoint = centerWidth - height + 57
let endXpoint = (centerWidth + height - 45)
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: startXpoint , y: 0))
// path.addCurve(to: CGPoint(x: centerWidth, y: height - 40),
// controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height - 40))
path.addCurve(to: CGPoint(x: centerWidth, y: height / 1.6),
controlPoint1: CGPoint(x: startXpoint - 5, y: height2 / 3), controlPoint2: CGPoint(x: (centerWidth - 10), y: height2 / 1.6))
path.addCurve(to: CGPoint(x: (centerWidth + height / 2.9 ), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height - 40), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
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 hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
for member in subviews.reversed() {
let subPoint = member.convert(point, from: self)
guard let result = member.hitTest(subPoint, with: event) else { continue }
return result
}
return nil
}
}
extension UITabBar {
override open func sizeThatFits(_ size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = 74
return sizeThatFits
}
}

Mask Image with custom UIBazierPath swift

I have created UIBezierPath with custom shape then I need to make it mask for image always I got empty image
here is my code
First I created the path, then create image and last create my mask but it is not working
here is image I need to mask it dropbox.com/s/tnxgx7g1uvb1zj7/TeethMask.png?dl=0 here is UIBazier path dropbox.com/s/nz93n1vgvj6c6y0/… I need to mask this image in this path
The output is something like this
https://www.dropbox.com/s/gueyhdmmdcfvyiq/image.png?dl=0
Here is ViewController class
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGR = UITapGestureRecognizer(target: self, action: #selector(didTap))
self.view.addGestureRecognizer(tapGR)
}
#objc func didTap(tapGR: UITapGestureRecognizer) {
let tapPoint = tapGR.location(in: self.view)
if #available(iOS 11.0, *) {
let shapeView = ShapeView(origin: tapPoint)
self.view.addSubview(shapeView)
} else {
// Fallback on earlier versions
}
}
}
Here is ShapeView class
import UIKit
#available(iOS 11.0, *)
class ShapeView: UIView {
let size: CGFloat = 150
let lineWidth: CGFloat = 3
var fillColor: UIColor!
var path: UIBezierPath!
init(origin: CGPoint) {
super.init(frame: CGRect(x: 0.0, y: 0.0, width: size, height: size))
self.fillColor = randomColor()
self.path = mouthPath()
self.center = origin
self.backgroundColor = UIColor.clear
}
func randomColor() -> UIColor {
let hue:CGFloat = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
return UIColor(hue: hue, saturation: 0.8, brightness: 1.0, alpha: 0.8)
}
func mouthPath() -> UIBezierPath{
let pointsArray = [CGPoint(x:36 , y:36 ),CGPoint(x:41 , y:36 ),CGPoint(x:45 , y:36 ),CGPoint(x:49 , y:36 ),CGPoint(x:53 , y:36 ),CGPoint(x:58 , y: 37),CGPoint(x:64 , y:37 ),CGPoint(x:69 , y:36 ),CGPoint(x:65 , y:29 ),CGPoint(x:58 , y:24 ),CGPoint(x:50 , y:22 ),CGPoint(x:42 , y:23 ),CGPoint(x:36 , y:28 ),CGPoint(x:32 , y:35 )]
let newPath = UIBezierPath()
let factor:CGFloat = 10
for i in 0...pointsArray.count - 1 { // last point is 0,0
let point = pointsArray[i]
let currentPoint1 = CGPoint(x: point.x * factor , y: point.y * factor)
if i == 0 {
newPath.move(to: currentPoint1)
} else {
newPath.addLine(to: currentPoint1)
}
}
newPath.addLine(to: CGPoint(x: pointsArray[0].x * factor, y: pointsArray[0].y * factor))
newPath.close()
let imageTemplate = UIImageView()
imageTemplate.image = UIImage(named: "TeethMask")
self.addSubview(imageTemplate)
self.bringSubviewToFront(imageTemplate)
imageTemplate.frame = self.frame
let mask = CAShapeLayer(layer: self.layer)
mask.frame = newPath.bounds
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.path = newPath.cgPath
mask.shouldRasterize = true
imageTemplate.layer.mask = mask
imageTemplate.layer.addSublayer(mask)
}
}
Well, you're doing a few things wrong...
The "teeth" image you linked:
has a native size of 461 x 259. So, I'm going to use a proportional "target" size of 200 x 112.
First, shape layers use 0,0 at upper-left. Your original points array:
let pointsArray = [
CGPoint(x: 36, y: 36),
CGPoint(x: 41, y: 36),
CGPoint(x: 45, y: 36),
CGPoint(x: 49, y: 36),
CGPoint(x: 53, y: 36),
CGPoint(x: 58, y: 37),
CGPoint(x: 64, y: 37),
CGPoint(x: 69, y: 36),
CGPoint(x: 65, y: 29),
CGPoint(x: 58, y: 24),
CGPoint(x: 50, y: 22),
CGPoint(x: 42, y: 23),
CGPoint(x: 36, y: 28),
CGPoint(x: 32, y: 35),
]
gives this shape:
If we invert the y-coordinates:
let pointsArray = [
CGPoint(x: 36.0, y: 23.0),
CGPoint(x: 41.0, y: 23.0),
CGPoint(x: 45.0, y: 23.0),
CGPoint(x: 49.0, y: 23.0),
CGPoint(x: 53.0, y: 23.0),
CGPoint(x: 58.0, y: 22.0),
CGPoint(x: 64.0, y: 22.0),
CGPoint(x: 69.0, y: 23.0),
CGPoint(x: 65.0, y: 30.0),
CGPoint(x: 58.0, y: 35.0),
CGPoint(x: 50.0, y: 37.0),
CGPoint(x: 42.0, y: 36.0),
CGPoint(x: 36.0, y: 31.0),
CGPoint(x: 32.0, y: 24.0),
]
we get this shape:
It will be difficult to get things to "line up" correctly if your shape is offset like that, so we can "normalize" the points to start at top-left:
let pointsArray: [CGPoint] = [
CGPoint(x: 4.0, y: 1.0),
CGPoint(x: 9.0, y: 1.0),
CGPoint(x: 13.0, y: 1.0),
CGPoint(x: 17.0, y: 1.0),
CGPoint(x: 21.0, y: 1.0),
CGPoint(x: 26.0, y: 0.0),
CGPoint(x: 32.0, y: 0.0),
CGPoint(x: 37.0, y: 1.0),
CGPoint(x: 33.0, y: 8.0),
CGPoint(x: 26.0, y: 13.0),
CGPoint(x: 18.0, y: 15.0),
CGPoint(x: 10.0, y: 14.0),
CGPoint(x: 4.0, y: 9.0),
CGPoint(x: 0.0, y: 2.0),
]
resulting in:
However, we want the shape to fit the image, so we can scale the UIBezierPath to the bounds of the imageView:
// need to scale the path to self.bounds
let scaleW = bounds.width / pth.bounds.width
let scaleH = bounds.height / pth.bounds.height
let trans = CGAffineTransform(scaleX: scaleW, y: scaleH)
pth.apply(trans)
and we're here:
The only thing left is to use that as a mask for the image.
I'm going to suggest subclassing UIImageView instead of UIView ... that way you can set the .image property without needing to add another view as a subview. Also, I think you'll find it much easier to manage the size of the custom, masked image in your controller code, rather than inside the custom class.
Here is a demo view controller and a custom MouthShapeView:
class TeethViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGR = UITapGestureRecognizer(target: self, action: #selector(didTap))
self.view.addGestureRecognizer(tapGR)
}
#objc func didTap(tapGR: UITapGestureRecognizer) {
let tapPoint = tapGR.location(in: self.view)
if #available(iOS 11.0, *) {
// make sure you can load the image
if let img = UIImage(named: "TeethMask") {
// create custom ShapeView with image
let shapeView = MouthShapeView(image: img)
// if you want to use original image proportions
// set the width you want and calculate a proportional height
// based on the original image size
let targetWidth: CGFloat = 200.0
let targetHeight: CGFloat = img.size.height / img.size.width * targetWidth
// set the frame size
shapeView.frame.size = CGSize(width: targetWidth, height: targetHeight)
// set the frame center
shapeView.center = tapPoint
// add it
self.view.addSubview(shapeView)
}
} else {
// Fallback on earlier versions
}
}
}
#available(iOS 11.0, *) class MouthShapeView: UIImageView {
let pointsArray: [CGPoint] = [
CGPoint(x: 4.0, y: 1.0),
CGPoint(x: 9.0, y: 1.0),
CGPoint(x: 13.0, y: 1.0),
CGPoint(x: 17.0, y: 1.0),
CGPoint(x: 21.0, y: 1.0),
CGPoint(x: 26.0, y: 0.0),
CGPoint(x: 32.0, y: 0.0),
CGPoint(x: 37.0, y: 1.0),
CGPoint(x: 33.0, y: 8.0),
CGPoint(x: 26.0, y: 13.0),
CGPoint(x: 18.0, y: 15.0),
CGPoint(x: 10.0, y: 14.0),
CGPoint(x: 4.0, y: 9.0),
CGPoint(x: 0.0, y: 2.0),
]
let maskLayer = CAShapeLayer()
override init(image: UIImage?) {
super.init(image: image)
maskLayer.fillColor = UIColor.black.cgColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let newPath = UIBezierPath()
pointsArray.forEach { p in
if p == pointsArray.first {
newPath.move(to: p)
} else {
newPath.addLine(to: p)
}
}
newPath.close()
// need to scale the path to self.bounds
let scaleW = bounds.width / newPath.bounds.width
let scaleH = bounds.height / newPath.bounds.height
let trans = CGAffineTransform(scaleX: scaleW, y: scaleH)
newPath.apply(trans)
maskLayer.path = newPath.cgPath
layer.mask = maskLayer
}
}
When you run that code, and tap on the view, you'll get this:

Custom change color of cell bottom border

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.

View is getting replaced while Scrolling in UITableView

I am an Android Developer and very new to iOS App Development. I am trying to build a simple chat system with only one line of data in each cell.
I am using a custom UIView class to generate a bubble and a UILabel and an UIImageView programmatically.
When I run the app for the first time, everything looks good. Please see the image below:
But, the problem occurs when I scroll the UITableView Upwards and I don't understand why the bubbles shift themselves towards the right side. See the image below:
Some post in SO said that it is one of the feature of UITableView.
How should I solve this?
If you need any code snippets from any part of the App, please comment below.
PS: I am using Xcode 9 and iOS 11.4 as testing device and Swift 4 programming.
Any help would be appreciated. Thanks.
Below is BubbleView Class:
EDIT
import UIKit
import Foundation
class BubbleView: UIView {
var incomingColor = UIColor(white: 0.9, alpha: 1)
var outgoingColor = UIColor(red: 0.09, green: 0.54, blue: 1, alpha: 1)
var isIncoming: Bool = false
init(isIncoming: Bool) {
self.isIncoming = isIncoming
super.init(frame: UIScreen.main.bounds);
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
override func draw(_ rect: CGRect) {
let width = rect.width
let height = rect.height
let bezierPath = UIBezierPath()
if self.isIncoming == true {
bezierPath.move(to: CGPoint(x: 22, y: height))
bezierPath.addLine(to: CGPoint(x: width - 17, y: height))
bezierPath.addCurve(to: CGPoint(x: width, y: height - 17), controlPoint1: CGPoint(x: width - 7.61, y: height), controlPoint2: CGPoint(x: width, y: height - 7.61))
bezierPath.addLine(to: CGPoint(x: width, y: 17))
bezierPath.addCurve(to: CGPoint(x: width - 17, y: 0), controlPoint1: CGPoint(x: width, y: 7.61), controlPoint2: CGPoint(x: width - 7.61, y: 0))
bezierPath.addLine(to: CGPoint(x: 21, y: 0))
bezierPath.addCurve(to: CGPoint(x: 4, y: 17), controlPoint1: CGPoint(x: 11.61, y: 0), controlPoint2: CGPoint(x: 4, y: 7.61))
bezierPath.addLine(to: CGPoint(x: 4, y: height - 11))
bezierPath.addCurve(to: CGPoint(x: 0, y: height), controlPoint1: CGPoint(x: 4, y: height - 1), controlPoint2: CGPoint(x: 0, y: height))
bezierPath.addLine(to: CGPoint(x: -0.05, y: height - 0.01))
bezierPath.addCurve(to: CGPoint(x: 11.04, y: height - 4.04), controlPoint1: CGPoint(x: 4.07, y: height + 0.43), controlPoint2: CGPoint(x: 8.16, y: height - 1.06))
bezierPath.addCurve(to: CGPoint(x: 22, y: height), controlPoint1: CGPoint(x: 16, y: height), controlPoint2: CGPoint(x: 19, y: height))
incomingColor.setFill()
} else {
bezierPath.move(to: CGPoint(x: width - 22, y: height))
bezierPath.addLine(to: CGPoint(x: 17, y: height))
bezierPath.addCurve(to: CGPoint(x: 0, y: height - 17), controlPoint1: CGPoint(x: 7.61, y: height), controlPoint2: CGPoint(x: 0, y: height - 7.61))
bezierPath.addLine(to: CGPoint(x: 0, y: 17))
bezierPath.addCurve(to: CGPoint(x: 17, y: 0), controlPoint1: CGPoint(x: 0, y: 7.61), controlPoint2: CGPoint(x: 7.61, y: 0))
bezierPath.addLine(to: CGPoint(x: width - 21, y: 0))
bezierPath.addCurve(to: CGPoint(x: width - 4, y: 17), controlPoint1: CGPoint(x: width - 11.61, y: 0), controlPoint2: CGPoint(x: width - 4, y: 7.61))
bezierPath.addLine(to: CGPoint(x: width - 4, y: height - 11))
bezierPath.addCurve(to: CGPoint(x: width, y: height), controlPoint1: CGPoint(x: width - 4, y: height - 1), controlPoint2: CGPoint(x: width, y: height))
bezierPath.addLine(to: CGPoint(x: width + 0.05, y: height - 0.01))
bezierPath.addCurve(to: CGPoint(x: width - 11.04, y: height - 4.04), controlPoint1: CGPoint(x: width - 4.07, y: height + 0.43), controlPoint2: CGPoint(x: width - 8.16, y: height - 1.06))
bezierPath.addCurve(to: CGPoint(x: width - 22, y: height), controlPoint1: CGPoint(x: width - 16, y: height), controlPoint2: CGPoint(x: width - 19, y: height))
outgoingColor.setFill()
}
bezierPath.close()
bezierPath.fill()
}
}
And, The tableView thingy:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BiddingHistoryIdentifier", for: indexPath) as! BiddingHistoryTableViewCell
let row = indexPath.row
var strNew: [String] = splitString[row].components(separatedBy: "-")
let strFirst = strNew[0].trimmingCharacters(in: .whitespacesAndNewlines)
let strSecond = strNew[1].trimmingCharacters(in: .whitespacesAndNewlines)
if !strFirst.elementsEqual(VendorLoginSetterGetter.strName.trimmingCharacters(in: .whitespacesAndNewlines)) {
let text = strSecond
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = .black
label.text = text
let constraintRect = CGSize(width: 0.66 * cell.frame.width,
height: .greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constraintRect,
options: .usesLineFragmentOrigin,
attributes: [.font: label.font],
context: nil)
label.frame.size = CGSize(width: ceil(boundingBox.width),
height: ceil(boundingBox.height))
let bubbleSize = CGSize(width: label.frame.width + 28,
height: label.frame.height + 20)
let bubbleView = BubbleView(isIncoming: true)
bubbleView.frame.size = bubbleSize
bubbleView.backgroundColor = .clear
//bubbleView.center = cell.center
bubbleView.frame.origin.y = (cell.frame.size.height / 2) - 20
bubbleView.frame.origin.x = 50
cell.addSubview(bubbleView)
label.center = bubbleView.center
cell.addSubview(label)
let imageView = UIImageView(frame: CGRect(x: 8, y: 0, width: 35, height: 35))
imageView.image = UIImage(named: "ic_vendor_black.png")
imageView.frame.origin.y = (cell.frame.size.height / 2) - 15
cell.addSubview(imageView)
}
else {
let text = strSecond
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = .white
label.text = text
let constraintRect = CGSize(width: 0.66 * cell.frame.width,
height: .greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constraintRect,
options: .usesLineFragmentOrigin,
attributes: [.font: label.font],
context: nil)
label.frame.size = CGSize(width: ceil(boundingBox.width),
height: ceil(boundingBox.height))
let bubbleSize = CGSize(width: label.frame.width + 28,
height: label.frame.height + 20)
let bubbleView = BubbleView(isIncoming: false)
bubbleView.frame.size = bubbleSize
bubbleView.backgroundColor = .clear
//bubbleView.center = cell.center
bubbleView.frame.origin.y = (cell.frame.size.height / 2) - 20
bubbleView.frame.origin.x = cell.frame.size.width - 200
cell.addSubview(bubbleView)
label.center = bubbleView.center
cell.addSubview(label)
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 28, height: 28))
imageView.image = UIImage(named: "ic_user_black.png")
imageView.frame.origin.y = (cell.frame.size.height / 2) - 15
imageView.frame.origin.x = cell.frame.size.width - 8
cell.addSubview(imageView)
}
return cell
}
Code Updated as Requested.
You need to move your code that adds the subviews to the cell from the cellForRowAt delegate to inside their respective cell classes.
The problem right now like #PeteMorris said, is that your cells which will be reused will already have the subviews which you added when it was dequeued the first time. So whenever the same cell is dequeued, subviews are added to it again.
The elegant solution would be to move the code that sets up the view to inside your cell class.
If you are looking for a hotfix you could remove all the subviews from the cell in prepareForReuse. (This method is called every time your cell is reused before dequeue-ing)
Your reuse method would look like this. (This method is inside your cell class)
override func prepareForReuse() {
super.prepareForReuse()
for view in subviews {
view.removeFromSuperview()
}
}

Adding inner shadow to top of UIView

I looked up but I couldn't find how I can add an inner shadow to UIView, only top (from top to bottom) for Swift. What is the best way add inner circle in Swift?
Edit: I've found some questions & answers on SO however they are either in obj-c or looks so complicated. I was just looking for a more Swifty way, if there is any
What I want to achieve:
Here's a pure Swift version that I whipped up:
public class EdgeShadowLayer: CAGradientLayer {
public enum Edge {
case Top
case Left
case Bottom
case Right
}
public init(forView view: UIView,
edge: Edge = Edge.Top,
shadowRadius radius: CGFloat = 20.0,
toColor: UIColor = UIColor.white,
fromColor: UIColor = UIColor.black) {
super.init()
self.colors = [fromColor.cgColor, toColor.cgColor]
self.shadowRadius = radius
let viewFrame = view.frame
switch edge {
case .Top:
startPoint = CGPoint(x: 0.5, y: 0.0)
endPoint = CGPoint(x: 0.5, y: 1.0)
self.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: shadowRadius)
case .Bottom:
startPoint = CGPoint(x: 0.5, y: 1.0)
endPoint = CGPoint(x: 0.5, y: 0.0)
self.frame = CGRect(x: 0.0, y: viewFrame.height - shadowRadius, width: viewFrame.width, height: shadowRadius)
case .Left:
startPoint = CGPoint(x: 0.0, y: 0.5)
endPoint = CGPoint(x: 1.0, y: 0.5)
self.frame = CGRect(x: 0.0, y: 0.0, width: shadowRadius, height: viewFrame.height)
case .Right:
startPoint = CGPoint(x: 1.0, y: 0.5)
endPoint = CGPoint(x: 0.0, y: 0.5)
self.frame = CGRect(x: viewFrame.width - shadowRadius, y: 0.0, width: shadowRadius, height: viewFrame.height)
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
To use it,
let topShadow = EdgeShadowLayer(forView: targetView, edge: .Top)
targetView.layer.addSublayer(topShadow)
Note that it defaults to a black-to-white gradient that's 20 points deep.
The full code, with a sample UIViewController that lets you toggle shadows on all four corners of a view, can be found at https://github.com/jrtibbetts/Tenebrae. I've also documented the EdgeShadowLayer pretty thoroughly.
I used implement inner shadow to UIView using Objective-C. I try to translate code into swift. Please forgive me for my poor swift syntax
you can call function below in UIView.didMoveToSuperview
func drawShadow() {
if nil == self.shadowLayer {
let size = self.frame.size
self.clipsToBounds = true
let layer: CALayer = CALayer()
layer.backgroundColor = UIColor.lightGrayColor().CGColor
layer.position = CGPointMake(size.width / 2, -size.height / 2 + 0.5)
layer.bounds = CGRectMake(0, 0, size.width, size.height)
layer.shadowColor = UIColor.darkGrayColor().CGColor
layer.shadowOffset = CGSizeMake(0.5, 0.5)
layer.shadowOpacity = 0.8
layer.shadowRadius = 5.0
self.shadowLayer = layer
self.layer.addSublayer(layer)
}
}
I tweaked the modification made by #anoop4real using clear as the toColor and made the interface more in-line with the shadow settings in CALayer, including defaults, with the exception of opacity, which is set to 0.0 by default. I went with a default of 0.6 since it looked the most natural.
extension UIView {
func addShadow(to edges: [UIRectEdge], radius: CGFloat = 3.0, opacity: Float = 0.6, color: CGColor = UIColor.black.cgColor) {
let fromColor = color
let toColor = UIColor.clear.cgColor
let viewFrame = self.frame
for edge in edges {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [fromColor, toColor]
gradientLayer.opacity = opacity
switch edge {
case .top:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: radius)
case .bottom:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.frame = CGRect(x: 0.0, y: viewFrame.height - radius, width: viewFrame.width, height: radius)
case .left:
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: radius, height: viewFrame.height)
case .right:
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.frame = CGRect(x: viewFrame.width - radius, y: 0.0, width: radius, height: viewFrame.height)
default:
break
}
self.layer.addSublayer(gradientLayer)
}
}
func removeAllShadows() {
if let sublayers = self.layer.sublayers, !sublayers.isEmpty {
for sublayer in sublayers {
sublayer.removeFromSuperlayer()
}
}
}
}
The top view is the default settings, and the bottom uses a radius of 5.0 to show more clearly.
view1.addShadow([.top, .bottom, .left, .right])
view2.addShadow([.top, .bottom, .left, .right], radius: 5.0)
view2.backgroundColor = .orange
I updated #NRitH's answer and made an extension out of it also modified so that you can manipulate multiple edges in one go
usage
myview.addShadow(to: [.top,.bottom], radius: 15.0)
extension UIView{
func addShadow(to edges:[UIRectEdge], radius:CGFloat){
let toColor = UIColor(colorLiteralRed: 235.0/255.0, green: 235.0/255.0, blue: 235.0/255.0, alpha: 1.0)
let fromColor = UIColor(colorLiteralRed: 188.0/255.0, green: 188.0/255.0, blue: 188.0/255.0, alpha: 1.0)
// Set up its frame.
let viewFrame = self.frame
for edge in edges{
let gradientlayer = CAGradientLayer()
gradientlayer.colors = [fromColor.cgColor,toColor.cgColor]
gradientlayer.shadowRadius = radius
switch edge {
case UIRectEdge.top:
gradientlayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: gradientlayer.shadowRadius)
case UIRectEdge.bottom:
gradientlayer.startPoint = CGPoint(x: 0.5, y: 1.0)
gradientlayer.endPoint = CGPoint(x: 0.5, y: 0.0)
gradientlayer.frame = CGRect(x: 0.0, y: viewFrame.height - gradientlayer.shadowRadius, width: viewFrame.width, height: gradientlayer.shadowRadius)
case UIRectEdge.left:
gradientlayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientlayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
case UIRectEdge.right:
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.5)
gradientlayer.frame = CGRect(x: viewFrame.width - gradientlayer.shadowRadius, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
default:
break
}
self.layer.addSublayer(gradientlayer)
}
}
func removeAllSublayers(){
if let sublayers = self.layer.sublayers, !sublayers.isEmpty{
for sublayer in sublayers{
sublayer.removeFromSuperlayer()
}
}
}
}
Swift 5 extension
extension UIView {
func addInnerShadow() {
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let radius = self.layer.cornerRadius
let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: 2, dy:2), cornerRadius:radius)
let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:radius).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties
innerShadow.shadowColor = UIColor.black.cgColor
innerShadow.shadowOffset = CGSize(width: 0, height: 0)
innerShadow.shadowOpacity = 0.5
innerShadow.shadowRadius = 2
innerShadow.cornerRadius = self.layer.cornerRadius
layer.addSublayer(innerShadow)
}
}
I rewrote #NRitH solution on Swift 3, also slightly refactor it:
final class SideShadowLayer: CAGradientLayer {
enum Side {
case top,
bottom,
left,
right
}
init(frame: CGRect, side: Side, shadowWidth: CGFloat,
fromColor: UIColor = .black,
toColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0),
opacity: Float = 0.5) {
super.init()
colors = [fromColor.cgColor, toColor.cgColor]
self.opacity = opacity
switch side {
case .bottom:
startPoint = CGPoint(x: 0.5, y: 1.0)
endPoint = CGPoint(x: 0.5, y: 0.0)
self.frame = CGRect(x: 0, y: frame.height - shadowWidth, width: frame.width, height: shadowWidth)
case .top:
startPoint = CGPoint(x: 0.5, y: 0.0)
endPoint = CGPoint(x: 0.5, y: 1.0)
self.frame = CGRect(x: 0, y: 0, width: frame.width, height: shadowWidth)
case .left:
startPoint = CGPoint(x: 0.0, y: 0.5)
endPoint = CGPoint(x: 1.0, y: 0.5)
self.frame = CGRect(x: 0, y: 0, width: shadowWidth, height: frame.height)
case .right:
startPoint = CGPoint(x: 1.0, y: 0.5)
endPoint = CGPoint(x: 0.0, y: 0.5)
self.frame = CGRect(x: frame.width - shadowWidth, y: 0, width: shadowWidth, height: frame.height)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
If you don't mind using clipsToBounds = true, you can create a new CALayer offset just off the edge of your view and add the shadow to THAT view. This is what J.Hunter's answer does.
J.Hunter's code adds a top shadow, here I updated it to Swift 5 and added it to the bottom.
Swift 5:
override func draw(_ rect: CGRect) {
// Create Inner Shadow. Not sure about efficiency of this.
// You may want to create a shadowLayer property
// and only run this code if it hasn't been created yet.
let size = rect.size
clipsToBounds = true // Don't want to see your fake view layer
let innerShadowLayer: CALayer = CALayer()
// Need to set a backgroundColor or it doesn't work
innerShadowLayer.backgroundColor = UIColor.black.cgColor
// Position your shadow layer (anchor point is in the center)
// on the edge of where your shadow needs to be.
// In my case this moves the shadow layer to the
// bottom edge of my view
innerShadowLayer.position = CGPoint(x: size.width / 2, y: size.height + (size.height / 2))
// This could be smaller I think, just copying J.Hunter's code...
innerShadowLayer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
// Normal shadow layer properties you'd use for an outer shadow
innerShadowLayer.shadowColor = UIColor.black.cgColor
innerShadowLayer.shadowOffset = CGSize(width: 0, height: 0)
innerShadowLayer.shadowOpacity = 0.3
innerShadowLayer.shadowRadius = 3
layer.addSublayer(innerShadowLayer)
}

Resources