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)
}
Related
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
}
}
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)
}
}
I'm learning RxSwift.
I added a border as CALayer to TextField. I would like to make this border reactively auto-resized as the textField is expanded (inputView changed to pickerView so I wish it could be resized by selecting titles)
I tried .rx.observe(CGRect.self, "frame") but it doesn't work properly.
extension UITextField {
func addBorderBottom(height: CGFloat, color: UIColor, width: CGFloat){
let border = CALayer()
border.frame = CGRect(x: 0, y: self.frame.height - height, width: width, height: height)
border.backgroundColor = color.cgColor
self.layer.addSublayer(border)
}
}
// viewContrller
func listDetailUISetup() {
let yellowColor = UIColor.hex(string: "FFB500", alpha: 1)
tagTextField.addBorderBottom(height: 1.0, color: yellowColor, width: tagTextField.frame.width)
}
//
picker.rx.modelSelected(String.self)
.subscribe(onNext: { tags in
self.tagTextField.text = tags[0]
})
.disposed(by: disposeBag)
tagTextField.rx.observe(CGRect.self, "frame")
.subscribe(onNext: { frame in
print("field frame changed")
let layer = self.tagTextField.layer.sublayers![0]
layer.frame = frame!
})
.disposed(by: disposeBag)
In a second way, I also tried to manually re-set a border after removing the previous one when a title in the pickerView is selected. But apparently, the width of the item selected at the previous time is reflected.
initial width
All's width
Favourite's width
icker.rx.modelSelected(String.self)
.subscribe(onNext: { tags in
self.tagTextField.text = tags[0]
self.tagTextField.layer.sublayers = nil
self.listDetailUISetup()
})
.disposed(by: disposeBag)
Hopefully, you could help to resolve this issue.
Thank you.
The frame property on a UIView is not KVO observable, so you can't observe it. The text field's layoutSubviews method is called whenever it changes size though so you can use that.
extension UIView {
func addDynamicBorderBottom(height: CGFloat, color: UIColor) -> Disposable {
let border = CALayer()
border.frame = CGRect(x: 0, y: frame.height - height, width: frame.width, height: height)
border.backgroundColor = color.cgColor
layer.addSublayer(border)
return rx.methodInvoked(#selector(UIView.layoutSubviews))
.bind(onNext: { [frame] _ in
border.frame = CGRect(x: 0, y: frame.height - height, width: frame.width, height: height)
})
}
}
But you would be better off making your border a UIView and setting up constraints to ensure it stays the right size.
I created an arbitrary view
let middleView = UIView(
frame: CGRect(x: 0.0,
y: view.frame.height/4,
width: view.frame.width,
height: view.frame.height/4))
middleView.backgroundColor = UIColor.blue
view.addSubview(middleView)
Then I created a circle using UIBezierPath; however when I set the position to middleView.center, the circle is far off to the bottom of the view. Can you set the position in the center of a subview?
let shapeLayer = CAShapeLayer()
let circlePath = UIBezierPath(
arcCenter: .zero,
radius: 100,
startAngle: CGFloat(0).toRadians(),
endAngle: CGFloat(360).toRadians(),
clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.purple.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.position = middleView.center
middleView.layer.addSublayer(shapeLayer)
How do I center this circle in that view?
You have two problems.
First, you are setting shapeLayer.position = middleView.center. The center of a view is is the superview's geometry. In other words, middleView.center is relative to view, not to middleView. But then you're adding shapeLayer as a sublayer of middleView.layer, which means shapeLayer needs a position that is in middleView's geometry, not in view's geometry. You need to set shapeLayer.position to the center of middleView.bounds:
shapeLayer.position = CGPoint(x: middleView.bounds.midX, y: middleView.bounds.midY)
Second, you didn't say where you're doing all this. My guess is you're doing it in viewDidLoad. But that is too early. In viewDidLoad, the views loaded from the storyboard still have the frames they were given in the storyboard, and haven't been laid out for the current device's screen size yet. So it's a bad idea to look at frame (or bounds or center) in viewDidLoad if you don't do something to make sure that things will be laid out correctly during the layout phase. Usually you do this by setting the autoresizingMask or creating constraints. Example:
let middleView = UIView(
frame: CGRect(x: 0.0,
y: view.frame.height/4,
width: view.frame.width,
height: view.frame.height/4))
middleView.backgroundColor = UIColor.blue
middleView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleTopMargin, .flexibleBottomMargin]
view.addSubview(middleView)
However, shapeLayer doesn't belong to a view, so it doesn't have an autoresizingMask and can't be constrained. You have to lay it out in code. You could do that, but it's better to just use a view to manage the shape layer. That way, you can use autoresizingMask or constraints to control the layout of the shape, and you can set it up in viewDidLoad.
let circleView = CircleView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
circleView.center = CGPoint(x: middleView.bounds.midX, y: middleView.bounds.midY)
circleView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
circleView.shapeLayer.strokeColor = UIColor.purple.cgColor
circleView.shapeLayer.fillColor = nil
middleView.addSubview(circleView)
...
class CircleView: UIView {
override class var layerClass: AnyClass { return CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
}
}
Result:
And after rotating to landscape:
I want to set corner radius on bottom left and right side with border on UIView. here is code. **issue is it's not display SHARP in 1.5 border **
class BorderView: UIView {
override func draw(_ rect: CGRect) {
let color = UIColor.red
let color2 = UIColor.brown
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 200, height: 200), byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
rectanglePath.close()
color2.setFill()
rectanglePath.fill()
color.set()
rectanglePath.lineWidth = 1.5
rectanglePath.stroke()
}
}
OutPut
expected output
If you use the border and corner properties on CALayer, it should do it all for you.
// Probably inside init
layer.borderWidth = 1.5
layer.borderColor = UIColor.red.cgColor
layer.cornerRadius = 10
layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]