I have a page for forgot password. It has only a text field asking the user to fill in their email address. The Designer designed the text field with top and bottom border only.
I tried answer from here UITextField Only Top And Bottom Border
but in the result it only shows bottom border for the text field.
Like in the image i would like to create a grey border for top and bottom
To remove Fights with views you could create a tableView with a static cell that contains a TextField. Voila done... Top and bottom border comes for free and you will use standard apple stuff :)
If you really want to draw the layers than follow the steps on your linked questions:
CALayer *topBorder = [CALayer layer];
topBorder.frame = CGRectMake(0, 0, self.frame.size.width, 1);
topBorder.backgroundColor = [UIColor blackColor].CGColor;
[myTextField.layer addSublayer:topBorder];
CALayer *bottomBorder = [CALayer layer];
bottomBorder.frame = CGRectMake(0, self.frame.size.height - 1, self.frame.size.width, 1);
bottomBorder.backgroundColor = [UIColor blackColor].CGColor;
[myTextField.layer addSublayer:bottomBorder];
In Swift:
let topBorder = CALayer()
topBorder.frame = CGRectMake(0, 0, bounds.size.width, 1)
topBorder.backgroundColor = UIColor.grayColor()
textField.layer.addSublayer(topBorder)
let bottomBorder = CALayer()
bottomBorder.frame = CGRectMake(0, bounds.size.height-1, bounds.size.width, 1)
bottomBorder.backgroundColor = UIColor.grayColor()
textField.layer.addSublayer(bottomBorder)
Thanks #El Captain for the valuable comment and nice answer by #Bjorn Ro even if it was in Objective-c i think.
And my answer for the question is (i'm using swift 2 Xcode 7)
Override the function viewDidLayoutSubviews() in your swift file. And the Code for the same is
override func viewDidLayoutSubviews() {
// Creates the bottom border
let borderBottom = CALayer()
let borderWidth = CGFloat(2.0)
borderBottom.borderColor = UIColor.grayColor().CGColor
borderBottom.frame = CGRect(x: 0, y: forgotPasswordEmailText.frame.height - 1.0, width: forgotPasswordEmailText.frame.width , height: forgotPasswordEmailText.frame.height - 1.0)
borderBottom.borderWidth = borderWidth
forgotPasswordEmailText.layer.addSublayer(borderBottom)
forgotPasswordEmailText.layer.masksToBounds = true
// Creates the Top border
let borderTop = CALayer()
borderTop.borderColor = UIColor.grayColor().CGColor
borderTop.frame = CGRect(x: 0, y: 0, width: forgotPasswordEmailText.frame.width, height: 1)
borderTop.borderWidth = borderWidth
forgotPasswordEmailText.layer.addSublayer(borderTop)
forgotPasswordEmailText.layer.masksToBounds = true
}
forgotPasswordEmailText is the text field for entering Email
The Final output looks like this... with a gray Colour border (Screen shot of iPhone 4s Simulator)
Good suggestions for programatic solution posted so far. But I figured I'd share an Interfacebuilder solution....
1) Create view collection in your view controller
#IBOutlet private var borderViews: [UIView]?
2) Create 2 UIViews in interface builder 1px high constrained to where you want them around the textfield
3) Connect the 2 views in interface builder to borderViews IBOutlet
4) Customise appearance of both views by using setValue forKeyPath... for example, on success you may want the border to turn green
setValue(UIColor.green, forKeyPath: "borderViews.backgroundColor")
In Swift 3 use extension:
Create Swift file
import UIKit
extension UITextField {
func setBottomBorder() {
self.borderStyle = .none
self.layer.backgroundColor = UIColor.white.cgColor
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 0.0
}
}
Call from anywhere:
PasswordField.setBottomBorder();
Here's a nice and easy Swift 4 implementation that handles resizing views as well :)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
viewToShadow.backgroundColor = .white
viewToShadow.layer.masksToBounds = false
viewToShadow.layer.sublayers?
.filter { layer -> Bool in
return layer.backgroundColor == UIColor.almostBlack.alpha(0.5).cgColor
}
.forEach { layer in
layer.removeFromSuperlayer()
}
[CGRect(x: 0.0, y: 0.0, width: viewToShadow.bounds.width, height: 0.5),
CGRect(x: 0.0, y: viewToShadow.bounds.height, width: viewToShadow.bounds.width, height: 0.5)]
.forEach { frame in
let layer = CALayer()
layer.frame = frame
layer.backgroundColor = UIColor.almostBlack.alpha(0.5).cgColor
viewToShadow.layer.addSublayer(layer)
}
}
Use handy extension for it
extension UITextField {
func addTopBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: 0, y: 0, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
self.borderStyle = UITextField.BorderStyle.none
self.layer.addSublayer(bottomLine)
}
func addBottomBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: 0, y: self.frame.size.height - 1, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "-", attributes: [NSAttributedString.Key.foregroundColor : #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)])
self.borderStyle = UITextField.BorderStyle.none
self.layer.addSublayer(bottomLine)
}
}
use it in you controller like this
yourTextfield.addTopBorder()
yourTextfield.addBottomBorder()
and don't forget to use it on main thread
DispatchQueue.main.async {
self.yourTextfield.addTopBorder()
self.yourTextfield.addBottomBorder()
}
Related
I am trying to make the background color of a UItextfield blurry. When I try the code below, my app crashes when it runs. Has anyone tried this before and knows how to make a UITextfield blurry?
let p = UITextField()
let blurEffect = UIBlurEffect(style: .light)
let blurView = UIVisualEffectView(effect: blurEffect)
p.layer.isOpaque = true
p.layer.backgroundColor = blurView as! CGColor
I found a solution where you place a view behind the UITextfield, and make it transparent.
let v = UIView()
v.frame = CGRect(x: 30, y: 100, width: 180, height: 30)
let blurEffect = UIBlurEffect(style: .light)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = v.bounds
blurView.backgroundColor = .clear
v.addSubview(blurView)
let p = UITextField()
p.frame = CGRect(x: 0, y: 0, width: 180, height: 30)
p.layer.isOpaque = true
p.backgroundColor = .clear
v.addSubview(p)
self.view.backgroundColor = .red
self.view.addSubview(v)
This is an example of proposed solution, with background image instead of red color, to emphasize the blur effect
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background") ?? UIImage())
The code below creates a red rectangle that is animated to move across the view from left to right. I would like to have an arbitrary shape loaded from an image to either superimpose or replace the rectangle. However, the circleLayer.contents = NSImage statement in the initializeCircleLayer function doesn't produce any effect. The diagnostic print statement seems to verify that the image exists and has been found, but no image appears in the view. How do I get an image into the layer to replace the animated red rectangle? Thanks!
CODE BELOW:
import Cocoa
class ViewController: NSViewController {
var circleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
initializeCircleLayer()
simpleCAAnimationDemo()
}
func initializeCircleLayer(){
circleLayer.bounds = CGRect(x: 0, y: 0, width: 150, height: 150)
circleLayer.position = CGPoint(x: 50, y: 150)
circleLayer.backgroundColor = NSColor.red.cgColor
circleLayer.cornerRadius = 10.0
let testIm = NSImage(named: NSImage.Name(rawValue: "testImage"))
print("testIm = \(String(describing: testIm))")
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
circleLayer.contentsGravity = kCAGravityCenter
self.view.layer?.addSublayer(circleLayer)
}
func simpleCAAnimationDemo(){
circleLayer.removeAllAnimations()
let animation = CABasicAnimation(keyPath: "position")
let startingPoint = NSValue(point: NSPoint(x: 50, y: 150))
let endingPoint = NSValue(point: NSPoint(x: 600, y: 150))
animation.fromValue = startingPoint
animation.toValue = endingPoint
animation.repeatCount = Float.greatestFiniteMagnitude
animation.duration = 10.0
circleLayer.add(animation, forKey: "linearMovement")
}
}
Why it doesn't work
The reason why
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
doesn't work is because it's a reference to the cgImage(forProposedRect:context:hints:) method, meaning that its type is
((UnsafeMutablePointer<NSRect>?, NSGraphicsContext?, [NSImageRep.HintKey : Any]?) -> CGImage?)?
You can see this by assigning NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage to a local variable and ⌥-clicking it to see its type.
The compiler allows this assignment because circleLayer.contents is an Any? property, so literally anything can be assigned to it.
How to fix it
As of macOS 10.6, you can assign NSImage objects to a layers contents directly:
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))
I have migrated my app from Swift 3.1 to Swift 4.0 (Xcode 8.3.3 to Xcode 9.0) and some part of my interface is broken now. Navigation Bar of Navigation controller is complete mess. Please, look at screenshot:
There are 3 elements:
left Netfnet logo (image)
right Signal strength (image)
right QR Code button
As you can see, two images are too big and not in center and button was deformed (it should be perfect square, all images too).
There is code which generated navigation controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
settings()
}
func settings() {
let color = UIColor(red: 81 / 255, green: 155 / 255, blue: 22 / 255, alpha: 1.0)
self.navigationController?.navigationBar.barTintColor = color
let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
logoImageView.contentMode = .scaleAspectFit
let logo = UIImage(named: "littleLogoImage")
logoImageView.image = logo
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: logoImageView)
let signalStengthImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
signalStengthImageView.contentMode = .scaleAspectFit
signalStengthImageView.image = UIImage(named: "signalStrength4")
let signalStengthImageItem = UIBarButtonItem(customView: signalStengthImageView)
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "qrCodeButton"), for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 35, height: 35)
let qrCodeButtonItem = UIBarButtonItem(customView: button)
navigationItem.rightBarButtonItems = [qrCodeButtonItem, signalStengthImageItem] //
}
}
I can decrease resolution of images directly myself, but I just don't get why everting was fine in Swift 3.1 and in Swift 4.0 is broken.
I will be thankful for any help or advice.
You have to add width and height constraints.
Your barImageView and barButton in CustomNavigationController should be like below :
func barImageView(imageName: String) -> UIBarButtonItem {
let imgView = imageView(imageName: imageName)
let widthConstraint = imgView.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = imgView.heightAnchor.constraint(equalToConstant: 35)
heightConstraint.isActive = true
widthConstraint.isActive = true
return UIBarButtonItem(customView: imgView)
}
func barButton(imageName: String, selector: Selector) -> UIBarButtonItem {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: imageName), for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 35, height: 35)
button.addTarget(self, action: selector, for: .touchUpInside)
let widthConstraint = button.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = button.heightAnchor.constraint(equalToConstant: 35)
heightConstraint.isActive = true
widthConstraint.isActive = true
return UIBarButtonItem(customView: button)
}
Your signalStengthImageView in LogoWithSignalStrength:
signalStengthImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
signalStengthImageView.contentMode = .scaleAspectFit
let widthConstraint = signalStengthImageView.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = signalStengthImageView.heightAnchor.constraint(equalToConstant: 35)
heightConstraint.isActive = true
widthConstraint.isActive = true
In Xcode 9, Navigation bar items are constraints base, Add this:
if #available(iOS 11.0, *) {
logoImageView.widthAnchor.constraint(equalToConstant: 35).isActive = true
logoImageView.heightAnchor.constraint(equalToConstant: 35).isActive = true
} else {
//set frames
}
Image of the pixelated text in the UITextView
Does anyone have any suggestions? The image of the issue is in the clickable link above.
Code:
struct Views {
static var name_field: UITextView?
}
In the viewDidLoad()
Views.name_field = UITextView(frame: CGRectMake(0, 0, name_field_width, 50))
Views.name_field!.textAlignment = NSTextAlignment.Center
Views.name_field!.font = UIFont.systemFontOfSize(15)
Views.name_field!.autocorrectionType = UITextAutocorrectionType.No
Views.name_field!.keyboardType = UIKeyboardType.Default
Views.name_field!.returnKeyType = .Done
Views.name_field!.delegate = self
Calling this function to style it
styleIt(Views.name_field!)
Adds a bottom border style and then sets the font, etc.
func styleIt(target: UITextView){
target.layer.backgroundColor = UIColor.clearColor().CGColor
let _border = CAShapeLayer()
_border.backgroundColor = UIColor.whiteColor().CGColor
_border.frame = CGRectMake(0, CGRectGetHeight(target.frame) - 1.0, CGRectGetWidth(target.frame), 1.0)
_border.shadowColor = UIColor.whiteColor().CGColor
_border.shadowOffset = CGSize(width: 3, height: 3)
_border.shadowOpacity = 0.23
_border.shadowRadius = 4
target.layer.addSublayer(_border)
target.font = UIFont(name: "ClementePDaa-Hairline", size: 24)
target.textColor = UIColor.whiteColor()
target.textContainerInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
applyPlaceholderStyle(target, placeholderText: _SEARCH_TEXT)
target.returnKeyType = .Done
target.frame = CGRectIntegral(target.frame)
target.layer.shouldRasterize = true
_border.shouldRasterize = true
target.textInputView.layer.shouldRasterize = true
}
This UITextView is a subview of search_field which is simply a UIView
search_field!.addSubview(Views.name_field!)
Your text view is blurry because the frame is using floating numbers.
To force integers value for your frame just do :
textView.frame = CGRectIntegral(textView.frame)
So I've created a custom NSButton to have a beautiful radio button, but I'm experiencing a very weird bug.
My radio button looks good in the playground, but when I add it to my project, it looks odd.
Here are screenshots:
Left = in the playground.
Right = in my project.
As you can see, on the right (in my project), the blue dot looks horrible, it's not smooth, same thing for the white circle (it's less visible with the dark background).
In my project, the NSShadow on my CALayer is also flipped, even if the geometryFlipped property on my main (_containerLayer_) CALayer is set to true. -> FIXED: see #Bannings answer.
import AppKit
extension NSColor {
static func colorWithDecimal(deviceRed deviceRed: Int, deviceGreen: Int, deviceBlue: Int, alpha: Float) -> NSColor {
return NSColor(
deviceRed: CGFloat(Double(deviceRed)/255.0),
green: CGFloat(Double(deviceGreen)/255.0),
blue: CGFloat(Double(deviceBlue)/255.0),
alpha: CGFloat(alpha)
)
}
}
extension NSBezierPath {
var CGPath: CGPathRef {
return self.toCGPath()
}
/// Transforms the NSBezierPath into a CGPathRef
///
/// :returns: The transformed NSBezierPath
private func toCGPath() -> CGPathRef {
// Create path
let path = CGPathCreateMutable()
var points = UnsafeMutablePointer<NSPoint>.alloc(3)
let numElements = self.elementCount
if numElements > 0 {
var didClosePath = true
for index in 0..<numElements {
let pathType = self.elementAtIndex(index, associatedPoints: points)
switch pathType {
case .MoveToBezierPathElement:
CGPathMoveToPoint(path, nil, points[0].x, points[0].y)
case .LineToBezierPathElement:
CGPathAddLineToPoint(path, nil, points[0].x, points[0].y)
didClosePath = false
case .CurveToBezierPathElement:
CGPathAddCurveToPoint(path, nil, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y)
didClosePath = false
case .ClosePathBezierPathElement:
CGPathCloseSubpath(path)
didClosePath = true
}
}
if !didClosePath { CGPathCloseSubpath(path) }
}
points.dealloc(3)
return path
}
}
class RadioButton: NSButton {
private var containerLayer: CALayer!
private var backgroundLayer: CALayer!
private var dotLayer: CALayer!
private var hoverLayer: CALayer!
required init?(coder: NSCoder) {
super.init(coder: coder)
self.setupLayers(radioButtonFrame: CGRectZero)
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
let radioButtonFrame = CGRect(
x: 0,
y: 0,
width: frameRect.height,
height: frameRect.height
)
self.setupLayers(radioButtonFrame: radioButtonFrame)
}
override func drawRect(dirtyRect: NSRect) {
}
private func setupLayers(radioButtonFrame radioButtonFrame: CGRect) {
//// Enable view layer
self.wantsLayer = true
self.setupBackgroundLayer(radioButtonFrame)
self.setupDotLayer(radioButtonFrame)
self.setupHoverLayer(radioButtonFrame)
self.setupContainerLayer(radioButtonFrame)
}
private func setupContainerLayer(frame: CGRect) {
self.containerLayer = CALayer()
self.containerLayer.frame = frame
self.containerLayer.geometryFlipped = true
//// Mask
let mask = CAShapeLayer()
mask.path = NSBezierPath(ovalInRect: frame).CGPath
mask.fillColor = NSColor.blackColor().CGColor
self.containerLayer.mask = mask
self.containerLayer.addSublayer(self.backgroundLayer)
self.containerLayer.addSublayer(self.dotLayer)
self.containerLayer.addSublayer(self.hoverLayer)
self.layer!.addSublayer(self.containerLayer)
}
private func setupBackgroundLayer(frame: CGRect) {
self.backgroundLayer = CALayer()
self.backgroundLayer.frame = frame
self.backgroundLayer.backgroundColor = NSColor.whiteColor().CGColor
}
private func setupDotLayer(frame: CGRect) {
let dotFrame = frame.rectByInsetting(dx: 6, dy: 6)
let maskFrame = CGRect(origin: CGPointZero, size: dotFrame.size)
self.dotLayer = CALayer()
self.dotLayer.frame = dotFrame
self.dotLayer.shadowColor = NSColor.colorWithDecimal(deviceRed: 46, deviceGreen: 146, deviceBlue: 255, alpha: 1.0).CGColor
self.dotLayer.shadowOffset = CGSize(width: 0, height: 2)
self.dotLayer.shadowOpacity = 0.4
self.dotLayer.shadowRadius = 2.0
//// Mask
let maskLayer = CAShapeLayer()
maskLayer.path = NSBezierPath(ovalInRect: maskFrame).CGPath
maskLayer.fillColor = NSColor.blackColor().CGColor
//// Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(origin: CGPointZero, size: dotFrame.size)
gradientLayer.colors = [
NSColor.colorWithDecimal(deviceRed: 29, deviceGreen: 114, deviceBlue: 253, alpha: 1.0).CGColor,
NSColor.colorWithDecimal(deviceRed: 59, deviceGreen: 154, deviceBlue: 255, alpha: 1.0).CGColor
]
gradientLayer.mask = maskLayer
//// Inner Stroke
let strokeLayer = CAShapeLayer()
strokeLayer.path = NSBezierPath(ovalInRect: maskFrame.rectByInsetting(dx: 0.5, dy: 0.5)).CGPath
strokeLayer.fillColor = NSColor.clearColor().CGColor
strokeLayer.strokeColor = NSColor.blackColor().colorWithAlphaComponent(0.12).CGColor
strokeLayer.lineWidth = 1.0
self.dotLayer.addSublayer(gradientLayer)
self.dotLayer.addSublayer(strokeLayer)
}
private func setupHoverLayer(frame: CGRect) {
self.hoverLayer = CALayer()
self.hoverLayer.frame = frame
//// Inner Shadow
let innerShadowLayer = CAShapeLayer()
let ovalPath = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -10, dy: -10))
let cutout = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -1, dy: -1)).bezierPathByReversingPath
ovalPath.appendBezierPath(cutout)
innerShadowLayer.path = ovalPath.CGPath
innerShadowLayer.shadowColor = NSColor.blackColor().CGColor
innerShadowLayer.shadowOpacity = 0.2
innerShadowLayer.shadowRadius = 2.0
innerShadowLayer.shadowOffset = CGSize(width: 0, height: 2)
self.hoverLayer.addSublayer(innerShadowLayer)
//// Inner Stroke
let strokeLayer = CAShapeLayer()
strokeLayer.path = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -0.5, dy: -0.5)).CGPath
strokeLayer.fillColor = NSColor.clearColor().CGColor
strokeLayer.strokeColor = NSColor.blackColor().colorWithAlphaComponent(0.22).CGColor
strokeLayer.lineWidth = 2.0
self.hoverLayer.addSublayer(strokeLayer)
}
}
let rbFrame = NSRect(
x: 87,
y: 37,
width: 26,
height: 26
)
let viewFrame = CGRect(
x: 0,
y: 0,
width: 200,
height: 100
)
let view = NSView(frame: viewFrame)
view.wantsLayer = true
view.layer!.backgroundColor = NSColor.colorWithDecimal(deviceRed: 40, deviceGreen: 40, deviceBlue: 40, alpha: 1.0).CGColor
let rb = RadioButton(frame: rbFrame)
view.addSubview(rb)
I'm using the exact same code on both my project and in the playground.
Here is a zip containing the playground and the project.
Just to be clear: I want to know why circles drawings are smooth in the playground but not in projects. (See #Bannings answer, it's more obvious with his screenshots)
Took time but I think I finally figured out everything or almost everything.
First some science : Circles or arcs can't be represented through Bézier curves. That's a property of Bézier curves as stated here : https://en.wikipedia.org/wiki/Bézier_curve
So when using NSBezierPath(ovalInRect:) you are in fact generating a Bézier curve approximating a circle. This can lead to a difference in appearance and how the shape renders. Still this shouldn't be a problem in our case because the difference is between two Bézier curves (the one in Playground and the one in real OS X project), but still I find it interesting to note in case you think the circle is not perfect enough.
Second as stated in this question (How to draw a smooth circle with CAShapeLayer and UIBezierPath) there are differences in the way the antialiasing will apply to your path depending on where the path is used. NSView's drawRect: being the place where the path antialiasing will be the best and CAShapeLayer being the worst.
Also I found that the CAShapeLayer documentation has a note saying this :
Shape rasterization may favor speed over accuracy. For example, pixels with multiple intersecting path segments may not give exact results.
Glen Low's answer to the question I previously mentioned seem to work fine in our case :
layer.rasterizationScale = 2.0 * self.window!.screen!.backingScaleFactor;
layer.shouldRasterize = true;
See the differences here :
Another solution is to leverage corner radiuses instead of Bézier paths in order to simulate a circle, and this time it's pretty accurate :
Finally my assumption on the difference between the Playground & the real OS X project is that Apple configured Playground so that some optimizations are turned off so the path even thought drawn using CAShapeLayer gets the best anti-aliasing possible. After all you're prototyping, performances are not really important especially on drawing operations.
I'm not sure to be right on this but I think it wouldn't be surprising. If anyone have any source I'd happily add it.
To me the best solution if you really need the best circle possible is to use corner radiuses.
Also as stated by #Bannings in another answer to this post. Shadows are reversed due to the fact playground render in a different coordinate system. See his answer to fix this.
I just fixed it by replace this line code:
self.dotLayer.shadowOffset = CGSize(width: 0, height: 2)
with:
self.dotLayer.shadowOffset = CGSize(width: 0, height: -2)
and replace innerShadowLayer.shadowOffset = CGSize(width: 0, height: 2) with:
innerShadowLayer.shadowOffset = CGSize(width: 0, height: -2)
I think you will get the same result like this:
Left = in the playground.
Right = in my project.
It seems the Playground is showing the bezier path in LLO coordinate system, you can visit the link:
https://forums.developer.apple.com/message/39277#39277