textfield bottom line issue in swift - swift

I am using the following code in Swift 4.2 to have Textfield bottom border:
extension UITextField {
func useUnderline() {
let border = CALayer()
let borderWidth = CGFloat(1.0)
border.borderColor = UIColor.lightGray.cgColor
border.frame = CGRect(origin: CGPoint(x: 0,y :self.frame.size.height - borderWidth), size: CGSize(width: self.frame.size.width, height: self.frame.size.height))
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
}
but in different model of iPhones I get a different behaviors. for example:
in Iphone XR:
and with iPhone X or 8:
Any solution to this issue would be appreciated.

I'm sure this problem does not depend on an iPhone model, but from the place where you call useUnderline(). If you will call useUnderline() from viewDidAppear, then the line will draw correct. Because the size of UIControls not yet actual when calling viewDidLoad and viewWillAppear.
But keep in your mind you will have a problem with your extension because sublayers will be added to UITextField on every viewDidAppear execution.

This could be a prime example where you must use a UITextField subclass instead of an extension. The accepted answer will work, but as suggested there, your TextField will get many layers added to it as the user uses his application.
The below subclass could solve this issue:
class CustomTextField: UITextField{
let border = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
useUnderline()
}
func useUnderline(){
if layer.sublayers?.contains(border) ?? false{
border.removeFromSuperlayer()
}
border.path = UIBezierPath.init(rect: CGRect.init(x: 0, y: self.bounds.height - 2, width: self.bounds.width, height: 2)).cgPath
border.fillColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.3).cgColor
self.layer.insertSublayer(border, at: 10)
}
}

Related

can't make round corners for UIView

I have a UIView object inside a horizontal stackView.
I'm calling this function inside LayoutSubviews:
func roundCorners(corners: UIRectCorner, radius: CurveRadius) {
var r = CGFloat()
switch radius {
case .small:
r = self.frame.width / 30
case .medium:
r = self.frame.width / 25
}
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: r, height: r))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
but it just doesn't work. I guess it has something to do with a stackView. can anyone help me out?
extension UIView {
/// Rounds ``UIView`` corners.
/// - Parameters:
/// - maskedCorners: Corners to be rounded.
/// - cornerRadius: Value to be set as corner radius.
func roundCorners(maskedCorners: CACornerMask,
cornerRadius: CGFloat) {
layer.cornerRadius = cornerRadius
layer.maskedCorners = maskedCorners
layer.masksToBounds = true
}
}
I'm not sure if the UIStackView is the problem here, but there's another, more re-usable way to do this using a CACornerMask on the UIView's layer in an extension:
extension UIView {
func roundCorners() {
self.layer.cornerRadius = 10
self.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
}
}
When you create your view, just call the roundCorners(), like this:
private lazy var roundCorneredView: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .red
view.roundCorners()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Then add that view to your hierarchy in the usual way using addSubview.
CACornerMask docs # Apple

how to remove border from UIVIEW

I want to know if there is a way to remove the border from view in swift after I have done this:
func addRightBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: self.frame.size.width - width,y: 0, width:width,
height:self.frame.size.height)
self.layer.addSublayer(border)
}
I have tried this ->
func removeBorder(){
self.layer.sublayers?.removeAll()
}
but it doesn't work
You should simply save your own reference to this layer, rather than using sublayers. Using sublayers is not prudent as there might be layers other than your border layer.
Thus:
weak var borderLayer: CALayer?
func addRightBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: bounds.maxX - width,
y: bounds.minY,
width: width,
height: bounds.height)
layer.addSublayer(border)
self.borderLayer = border
}
func removeBorder() {
borderLayer?.removeFromSuperlayer()
}
This obviously assumes that this was for a subclass of UIView and not an extension. If for the latter, you’d have to use an associated object to keep track of the border layer.
Note, one should not use frame when figuring out where to place the sublayer. Always use bounds.
Taking it a step further, you may want it to gracefully respond to frame changes, too:
override func layoutSubviews {
super.layoutSubviews()
borderLayer?.frame = CGRect(x: bounds.maxX - width,
y: bounds.minY,
width: width,
height: bounds.height)
}

How to order views' layers on top of each others in Swift

I added the redView as a subView of the textField and used AutoLayout to constraint it to be as it appears in the image, but the problem here now is
I want to bring the red view to be on top of the border
I tried to use this method
bringSubviewToFront(floatingPlaceholderContainer)
but it didn't work, also tried to use the layer.zPosition = .greatestFiniteMagnitude and it also didn't work
Edit-
the code I used and didn't work as I expected
override func layoutSubviews() {
super.layoutSubviews()
self.redlayer.backgroundColor = UIColor.red.cgColor
self.redlayer.frame = CGRect(x: 10, y: -10, width: 100, height: 30)
self.layer.addSublayer(self.redlayer)
self.redlayer.zPosition = 10
}
and this is the result
You need to configure the timing of when you're adding this layer. This approach works fine for me:
class MyTextField : UITextField {
let redlayer = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.redlayer.backgroundColor = UIColor.red.cgColor
self.redlayer.frame = CGRect(x: 10, y: -10, width: 100, height: 30)
self.layer.addSublayer(self.redlayer)
self.redlayer.zPosition = 10
}
}

Force Custom NSView from PaintCode to Redraw

I created an icon in PaintCode that is drawn programmatically (but this question isn't necessarily specific to that tool) and I'm trying to get that icon to redraw.
I use a custom class like this:
class IconTabGeneral: NSView {
override func draw(_ dirtyRect: NSRect) {
StyleKitMac.drawTabGeneral()
}
}
The drawTabGeneral() is a method in the StyleKitMac class (generated by PaintCode) that looks like this (I'll omit all the bezierPath details):
#objc dynamic public class func drawTabGeneral(frame targetFrame: NSRect = NSRect(x: 0, y: 0, width: 22, height: 22), resizing: ResizingBehavior = .aspectFit) {
//// General Declarations
let context = NSGraphicsContext.current!.cgContext
//// Resize to Target Frame
NSGraphicsContext.saveGraphicsState()
let resizedFrame: NSRect = resizing.apply(rect: NSRect(x: 0, y: 0, width: 22, height: 22), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 22, y: resizedFrame.height / 22)
//// Bezier Drawing
let bezierPath = NSBezierPath()
...
bezierPath.close()
StyleKitMac.accentColor.setFill() ⬅️Custom color set here
bezierPath.fill()
NSGraphicsContext.restoreGraphicsState()
}
The accentColor defined there is a setting that can be changed by the user. I can't get my instance of IconTabGeneral to redraw to pick up the new color after the user changes it.
I've tried this without any luck:
iconGeneralTabInstance.needsDisplay = true
My understanding is that needsDisplay would force the draw function to fire again, but apparently not.
Any idea how I can get this icon to redraw and re-fill its bezierPath?
How about using a NSImageView? Here is a working example:
import AppKit
enum StyleKit {
static func drawIcon(frame: CGRect) {
let circle = NSBezierPath(ovalIn: frame.insetBy(dx: 1, dy: 1))
NSColor.controlAccentColor.setFill()
circle.fill()
}
}
let iconView = NSImageView()
iconView.image = .init(size: .init(width: 24, height: 24), flipped: true) { drawingRect in
StyleKit.drawIcon(frame: drawingRect)
return true
}
import PlaygroundSupport
PlaygroundPage.current.liveView = iconView
I think I got it. It turns out the PaintCode-generated StyleKitMac class was caching the color. The icon was, in fact, being redrawn after needsDisplay was being set. So I just had to refresh the cache's color value.

Converting a CAShapeLayer to use in an NSImageView

I'm trying to port some iOS code for a Mac app. My code is as follows:
func innerRing() {
let innerRing = CAShapeLayer()
let circleRadius: CGFloat = 105.0
innerRing.frame = InnerRingView.bounds
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = CGRectGetMidX(InnerRingView.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(InnerRingView.bounds) - CGRectGetMidY(circleFrame)
return circleFrame
}
innerRing.path = UIBezierPath(ovalInRect: circleFrame()).CGPath
innerRing.lineWidth = 3.0
innerRing.strokeStart = 0.0
innerRing.strokeEnd = 1.0
innerRing.fillColor = UIColor.clearColor().CGColor
innerRing.strokeColor = UIColor(red: 147.0/255.0, green: 184.0/255.0, blue: 255.0/255.0, alpha: 1.0).CGColor
InnerRingView.layer.addSublayer(innerRing)
}
This code works very well, especially for adjusting the fill color, stroke color, and stroke start/end.
In my Mac app, I am effectively trying to use the same code, but apply it to an NSImageView (I want it to be able to appear on each row of a table, and I will adjust certain parameters (such as color) based on what that row details.
Could anyone assist with guidance on adding this simple circle to an NSImageView?
Why do you want to use an NSImageView? NSImageView is for displaying images (icons, pictures, etc).
Make yourself a custom NSView instead. Just remember that, unlike UIKit's UIView, NSView doesn't get a layer by default, so you need to tell It to by setting wantsLayer to true.
Like so:
class CircleView: NSView {
lazy var innerRing: CAShapeLayer = {
let innerRing = CAShapeLayer()
let circleRadius: CGFloat = 105.0
innerRing.frame = self.bounds
var circleFrame = CGRect(x: 0, y: 0, width: circleRadius, height: circleRadius)
circleFrame.origin.x = CGRectGetMidX(self.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(self.bounds) - CGRectGetMidY(circleFrame)
innerRing.path = CGPathCreateWithEllipseInRect(circleFrame, nil)
innerRing.lineWidth = 3.0
innerRing.strokeStart = 0.0
innerRing.strokeEnd = 1.0
innerRing.fillColor = NSColor.clearColor().CGColor
innerRing.strokeColor = NSColor(red: 147.0/255.0, green: 184.0/255.0, blue: 255.0/255.0, alpha: 1.0).CGColor
return innerRing
}()
override func awakeFromNib() {
super.awakeFromNib()
wantsLayer = true
layer = CALayer()
layer?.addSublayer(innerRing)
}
}