draw lines on all rows of a UITextView - ios

I want to draw a line in UITextView to separate lines like a notebook.
After researching, I came across the following site, but the information was out of date and could not be applied to the current version.
Is it possible to draw lines on the UITextView?

One approach is to create a UITextView subclass and override draw(_ rect: CGRect).
Here is an example that should get you on your way:
class LinedTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// this is needed to tell the lines to redraw
// when the text is scrolled
self.contentMode = .redraw
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext(),
let font = self.font
else {
super.draw(rect)
return
}
context.setStrokeColor(UIColor.red.cgColor)
context.setLineWidth(1.0)
context.beginPath()
let nLines = (self.contentSize.height + self.bounds.height) / font.lineHeight
var y: CGFloat = font.lineHeight + textContainerInset.top
for _ in 0..<Int(nLines) {
context.move(to: .init(x: 0.0, y: y))
context.addLine(to: .init(x: bounds.maxX, y: y))
y += font.lineHeight
}
context.strokePath()
}
}
You use it just like a normal UITextView as in this example view controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testTextView = LinedTextView()
testTextView.backgroundColor = .yellow
testTextView.text = "When a user taps a text view, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text view can handle the input in an application-specific way. You can specify attributes, such as font, color, and alignment, that apply to all text in a text view."
testTextView.font = .systemFont(ofSize: 28.0, weight: .regular)
testTextView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testTextView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
testTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
testTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
testTextView.heightAnchor.constraint(equalToConstant: 300.0),
])
}
}
Looks like this when running:
Please note: this is Example Code Only!!! - it is intended to give you something to learn from and should not be considered "production ready."

Related

How to animate a UILabel that resizes in parallel with its container view

I am trying to animate a multi-line label inside a UIView. In the container view, the width of the label is relative to the bounds. When the container view is animated, the label jumps to the final state and then the container resizes. How can I instead animate the right side of the text to be continuously pinned to the right edge of the container view as it grows larger?
class ViewController: UIViewController {
var container: ContainerView = ContainerView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
container.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
container.center = view.center
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut) {
self.container.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
self.container.center = self.view.center
self.container.layoutIfNeeded()
}
}
}
}
class ContainerView: UIView {
let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.text = "foo bar foo bar foo bar foo bar foo bar foo bar foo foo bar foo bar foo bar foo bar foo bar foo bar foo"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .purple
addSubview(label)
}
override func layoutSubviews() {
super.layoutSubviews()
let size = label.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
label.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: size.height)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you've seen, when we change the width of a label UIKit re-calculates the word wrapping immediately.
When we do something like this:
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut) {
self.container.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
self.container.center = self.view.center
self.container.layoutIfNeeded()
}
UIKit sets the width and then animates it. So, as soon as the animation starts, the word wrapping gets set to the "destination" width.
One way to animate the word wrap changes would be to create an animation loop, using small point-size changes.
That works-ish, with two problems:
Using a UILabel, we get vertical shifting (because the text is vertically centered in a label), and
If we make the incremental size changes small, it's smooth but slow. If we make the incremental changes large, it's quick but "jerky."
To solve the first problem, we can use a UITextView, subclassed to work like a top-aligned UILabel. Here's an example:
class MyTextViewLabel: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() -> Void {
isScrollEnabled = false
isEditable = false
isSelectable = false
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
Not much we can do about the second problem, other than experiment with the width-increment value.
Here's a complete example to look at and play with (using the above MyTextViewLabel class). Note that I'm also using auto-layout / constraints instead of explicit frames:
class MyContainerView: UIView {
let label: MyTextViewLabel = {
let label = MyTextViewLabel()
label.text = "Let's use some readable text for this example. It will make the wrapping changes look more natural than using a bunch of repeating three-character \"words.\""
// let's set the font to the default UILabel font
label.font = .systemFont(ofSize: 17.0)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
clipsToBounds = true
backgroundColor = .purple
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// let's inset the "label" by 4-points so we can see the purple view frame
label.topAnchor.constraint(equalTo: topAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4.0),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4.0),
// if we want the bottom text to be "clipped"
// don't set the bottom anchor
//label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4.0),
])
label.backgroundColor = .yellow
}
}
class LabelWrapAnimVC: UIViewController {
// for this example
let startWidth: CGFloat = 150.0
let targetWidth: CGFloat = 200.0
// number of points to increment in each loop
// play with this value...
// 1-point produces a very smooth result, but the total animation time will be slow
// 5-points seems "reasonable" (looks smoother on device than on simulator)
let loopIncrement: CGFloat = 5.0
// total amount of time for the animation
let loopTotalDuration: TimeInterval = 2.0
// each loop anim duration - will be calculated
var loopDuration: TimeInterval = 0
let container: MyContainerView = MyContainerView()
var cWidth: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
cWidth = container.widthAnchor.constraint(equalToConstant: startWidth)
NSLayoutConstraint.activate([
container.centerXAnchor.constraint(equalTo: g.centerXAnchor),
container.centerYAnchor.constraint(equalTo: g.centerYAnchor),
container.heightAnchor.constraint(equalTo: container.widthAnchor),
cWidth,
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
doAnim()
}
func animLoop() {
cWidth.constant += loopIncrement
// in case we go over the target width
cWidth.constant = min(cWidth.constant, targetWidth)
UIView.animate(withDuration: loopDuration, animations: {
self.view.layoutIfNeeded()
}, completion: { _ in
if self.cWidth.constant < self.targetWidth {
self.animLoop()
} else {
// maybe do something when animation is done
}
})
}
func doAnim() {
// reset width to original
cWidth.constant = startWidth
// calculate loop duration based on size difference
let numPoints: CGFloat = targetWidth - startWidth
let numLoops: CGFloat = numPoints / loopIncrement
loopDuration = loopTotalDuration / numLoops
DispatchQueue.main.async {
self.animLoop()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
doAnim()
}
}
I don't know if this will be suitable for your target usage, but it's at least worth a look.

inputAccessoryView, API error? _UIKBCompatInputView? UIViewNoIntrinsicMetric, simple code, can't figure out

Help me in one of the two ways maybe:
How to solve the problem? or
How to understand the error message?
Project summary
So I'm learning about inputAccessoryView by making a tiny project, which has only one UIButton. Tapping the button summons the keyboard with inputAccessoryView which contains 1 UITextField and 1 UIButton. The UITextField in the inputAccessoryView will be the final firstResponder that is responsible for the keyboard with that inputAccessoryView
The error message
API error: <_UIKBCompatInputView: 0x7fcefb418290; frame = (0 0; 0 0); layer = <CALayer: 0x60000295a5e0>> returned 0 width, assuming UIViewNoIntrinsicMetric
The code
is very straightforward as below
The custom UIView is used as inputAccessoryView. It installs 2 UI outlets, and tell responder chain that it canBecomeFirstResponder.
class CustomTextFieldView: UIView {
let doneButton:UIButton = {
let button = UIButton(type: .close)
return button
}()
let textField:UITextField = {
let textField = UITextField()
textField.placeholder = "placeholder"
return textField
}()
required init?(coder: NSCoder) {
super.init(coder: coder)
initSetup()
}
override init(frame:CGRect) {
super.init(frame: frame)
initSetup()
}
convenience init() {
self.init(frame: .zero)
}
func initSetup() {
addSubview(doneButton)
addSubview(textField)
}
func autosizing(to vc: UIViewController) {
frame = CGRect(x: 0, y: 0, width: vc.view.frame.size.width, height: 40)
let totalWidth = frame.size.width - 40
doneButton.frame = CGRect(x: totalWidth * 4 / 5 + 20,
y: 0,
width: totalWidth / 5,
height: frame.size.height)
textField.frame = CGRect(x: 20,
y: 0,
width: totalWidth * 4 / 5,
height: frame.size.height)
}
override var canBecomeFirstResponder: Bool { true }
override var intrinsicContentSize: CGSize {
CGSize(width: 400, height: 40)
} // overriding this variable seems to have no effect.
}
Main VC uses the custom UIView as inputAccessoryView. The UITextField in the inputAccessoryView becomes the real firstResponder in the end, I believe.
class ViewController: UIViewController {
let customView = CustomTextFieldView()
var keyboardShown = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
customView.autosizing(to: self)
}
#IBAction func summonKeyboard() {
print("hello")
keyboardShown = true
self.becomeFirstResponder()
customView.textField.becomeFirstResponder()
}
override var canBecomeFirstResponder: Bool { keyboardShown }
override var inputAccessoryView: UIView? {
return customView
}
}
I've seen people on the internet says this error message will go away if I run on a physical phone. I didn't go away when I tried.
I override intrinsicContentSize of the custom view, but it has no effect.
The error message shows twice together when I tap summon.
What "frame" or "layer" does the error message refer to? Does it refer to the custom view's frame and layer?
If we use Debug View Hierarchy we can see that _UIKBCompatInputView is part of the (internal) view hierarchy of the keyboard.
It's not unusual to see constraint errors / warnings with internal views.
Since frame and/or intrinsic content size seem to have no effect, I don't think it can be avoided (nor does it seem to need to be).
As a side note, you can keep the "Done" button round by using auto-layout constraints. Here's an example:
class CustomTextFieldView: UIView {
let textField: UITextField = {
let tf = UITextField()
tf.font = .systemFont(ofSize: 16)
tf.autocorrectionType = .no
tf.returnKeyType = .done
tf.placeholder = "placeholder"
// textField backgroundColor so we can see its frame
tf.backgroundColor = .yellow
return tf
}()
let doneButton:UIButton = {
let button = UIButton(type: .close)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
autoresizingMask = [.flexibleHeight, .flexibleWidth]
[doneButton, textField].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
addSubview(v)
}
NSLayoutConstraint.activate([
// constrain doneButton
// Trailing: 20-pts from trailing
doneButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
// Top and Bottom 8-pts from top and bottom
doneButton.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
doneButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
// Width equal to default height
// this will keep the button round instead of oval
doneButton.widthAnchor.constraint(equalTo: doneButton.heightAnchor),
// constrain textField
// Leading: 20-pts from leading
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
// Trailing: 8-pts from doneButton leading
textField.trailingAnchor.constraint(equalTo: doneButton.leadingAnchor, constant: -8.0),
// vertically centered
textField.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
}
class CustomTextFieldViewController: UIViewController {
let customView = CustomTextFieldView()
var keyboardShown = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func summonKeyboard() {
print("hello")
keyboardShown = true
self.becomeFirstResponder()
customView.textField.becomeFirstResponder()
}
override var canBecomeFirstResponder: Bool { keyboardShown }
override var inputAccessoryView: UIView? {
return customView
}
}

UIScrollView draw ruler using drawRect

I am trying to draw a ruler on top of UIScrollView. The way I do it is by adding a custom view called RulerView. I add this rulerView to superview of scrollView setting its frame to be same as frame of scrollView. I then do custom drawing to draw lines as scrollView scrolls. But the drawing is not smooth, it stutters as I scroll and the end or begin line suddenly appears/disappears. What's wrong in my drawRect?
class RulerView: UIView {
public var contentOffset = CGFloat(0) {
didSet {
self.setNeedsDisplay()
}
}
public var contentSize = CGFloat(0)
let smallLineHeight = CGFloat(4)
let bigLineHeight = CGFloat(10)
override open func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
UIColor.white.set()
let contentWidth = max(rect.width, contentSize)
let lineGap:CGFloat = 5
let totalNumberOfLines = Int(contentWidth/lineGap)
let startIndex = Int(contentOffset/lineGap)
let endIndex = Int((contentOffset + rect.width)/lineGap)
let beginOffset = contentOffset - CGFloat(startIndex)*lineGap
if let context = UIGraphicsGetCurrentContext() {
for i in startIndex...endIndex {
let path = UIBezierPath()
path.move(to: CGPoint(x: beginOffset + CGFloat(i - startIndex)*lineGap , y:0))
path.addLine(to: CGPoint(x: beginOffset + CGFloat(i - startIndex)*lineGap, y: i % 5 == 0 ? bigLineHeight : smallLineHeight))
path.lineWidth = 0.5
path.stroke()
}
}
}
And in the scrollview delegate, I set this:
//MARK:- UIScrollViewDelegate
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
rulerView.contentSize = scrollView.contentSize.width
rulerView.contentOffset = offset
}
Your override func draw(_ rect: CGRect) is very "heavy." I think you'll get much better performance by using a shape layer for your "tick marks" and letting UIKit handle the drawing.
Edit - as per comments
Added numbering to the tick marks using CATextLayer as sublayers.
Here's a sample RulerView (using your tick mark dimensions and spacing):
class RulerView: UIView {
public var contentOffset: CGFloat = 0 {
didSet {
layer.bounds.origin.x = contentOffset
}
}
public var contentSize = CGFloat(0) {
didSet {
updateRuler()
}
}
let smallLineHeight: CGFloat = 4
let bigLineHeight: CGFloat = 10
let lineGap:CGFloat = 5
// numbers under the tick marks
// with 12-pt system font .light
// 40-pt width will fit up to 5 digits
let numbersWidth: CGFloat = 40
let numbersFontSize: CGFloat = 12
var shapeLayer: CAShapeLayer!
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
shapeLayer = self.layer as? CAShapeLayer
// these properties don't change
backgroundColor = .clear
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = 0.5
shapeLayer.masksToBounds = true
}
func updateRuler() -> Void {
// size is set by .fontSize, so ofSize here is ignored
let numbersFont = UIFont.systemFont(ofSize: 1, weight: .light)
let pth = UIBezierPath()
var x: CGFloat = 0
var i = 0
while x < contentSize {
pth.move(to: CGPoint(x: x, y: 0))
pth.addLine(to: CGPoint(x: x, y: i % 5 == 0 ? bigLineHeight : smallLineHeight))
// number every 10 ticks - change as desired
if i % 10 == 0 {
let layer = CATextLayer()
layer.contentsScale = UIScreen.main.scale
layer.font = numbersFont
layer.fontSize = numbersFontSize
layer.alignmentMode = .center
layer.foregroundColor = UIColor.white.cgColor
// if we want to number by tick count
layer.string = "\(i)"
// if we want to number by point count
//layer.string = "\(i * Int(lineGap))"
layer.frame = CGRect(x: x - (numbersWidth * 0.5), y: bigLineHeight, width: numbersWidth, height: numbersFontSize)
shapeLayer.addSublayer(layer)
}
x += lineGap
i += 1
}
shapeLayer.path = pth.cgPath
}
}
and here's a sample controller class to demonstrate:
class RulerViewController: UIViewController, UIScrollViewDelegate {
var rulerView: RulerView = RulerView()
var scrollView: UIScrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
[scrollView, rulerView].forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
// sample scroll content will be a horizontal stack view
// with 30 labels
// spaced 20-pts apart
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.spacing = 20
for i in 1...30 {
let v = UILabel()
v.textAlignment = .center
v.backgroundColor = .yellow
v.text = "Label \(i)"
stack.addArrangedSubview(v)
}
scrollView.addSubview(stack)
let g = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// scroll view 20-pts Top / Leading / Trailing
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// scroll view Height: 60-pts
scrollView.heightAnchor.constraint(equalToConstant: 60.0),
// stack view 20-pts Top, 0-pts Leading / Trailing / Bottom (to scroll view's content layout guide)
stack.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
stack.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
stack.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
// ruler view 4-pts from scroll view Bottom
rulerView.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 4.0),
rulerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
// ruler view 0-pts from scroll view Leading / Trailing (equal width and horizontal position of scroll view)
rulerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
// ruler view Height: 24-pts (make sure it's enough to accomodate ruler view's bigLineHeight plus numbering height)
rulerView.heightAnchor.constraint(equalToConstant: 24.0),
])
scrollView.delegate = self
// so we can see the sroll view frame
scrollView.backgroundColor = .red
// if we want to see the rulerView's frame
//rulerView.backgroundColor = .brown
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// this is when we know the scroll view's content size
rulerView.contentSize = scrollView.contentSize.width
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// update rulerView's x-offset
rulerView.contentOffset = scrollView.contentOffset.x
}
}
Output:
the tick marks (and numbers) will, of course, scroll left-right synched with the scroll view.

how to remove safe area from UIView Programmatically in Swift?

This is a custom view, this view creates a square with a given frame with the background color. I am adding a custom view to a subview, the view appears properly. But I am not able to cover the bottom safe area, anyone can help me to remove the safe area from bottom Programmatically.
class CustomView: UIView {
override var frame: CGRect {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7).setFill()
UIRectFill(rect)
let square = UIBezierPath(rect: CGRect(x: 200, y: rect.size.height/2 - 150/2, width: UIScreen.main.bounds.size.width - 8, height: 150))
let dashPattern : [CGFloat] = [10, 4]
square.setLineDash(dashPattern, count: 2, phase: 0)
UIColor.white.setStroke()
square.lineWidth = 5
square.stroke()
}
}
Consider the following example:
class ViewController: UIViewController {
private let myView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
configureCustomView()
}
private func configureCustomView() {
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.backgroundColor = .systemPurple
NSLayoutConstraint.activate([
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
myView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
myView.heightAnchor.constraint(equalToConstant: 200)
])
}
}
Result:
If you don't want to go over the safe area, then you could use myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) inside NSLayoutConstraint.activate([...]).
So you actually don't have to remove the SafeArea, because you can simply ignore them if you want so...
In case you want to do this fast. (Not programatically)
Open storyboard.
Select your UIView.
Safe Area should be unselected.

How to add with to border UITextField Swift when when create Rounded corners

I want to create a rounded corner "Oval shape" UITextField. I have a problem with the inner border is not rounded and give strange log to the UITextField when the background of the UITextField same as the view
How border looks:
Image from the MockAUp what I want to achieve:
Swift code:
txtfEmail.layer.cornerRadius = 26
txtfEmail.clipsToBounds = true
txtfEmail.attributedPlaceholder = NSAttributedString(string: "Email",
attributes: [NSAttributedString.Key.foregroundColor: UIColor.white.withAlphaComponent(0.5)])
email UITextfield inspectere
One approach would be to create a custom UIView class with an embedded text field.
Here's an example, using #IBInspectable and #IBDesignable to let you see it during Storyboard design:
import UIKit
#IBDesignable
class RoundedTextField: UIView {
#IBInspectable var placeholder: String = "" {
didSet {
textField.attributedPlaceholder = NSAttributedString(string: placeholder,
attributes: [NSAttributedString.Key.foregroundColor: UIColor.white.withAlphaComponent(0.5)])
}
}
let textField: UITextField = {
let v = UITextField()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override func prepareForInterfaceBuilder() {
commonInit()
}
func commonInit() -> Void {
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 2
layer.masksToBounds = true
addSubview(textField)
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: topAnchor, constant: 12.0),
textField.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12.0),
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
])
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.size.height * 0.5
}
}
Result in Storyboard / Interface Builder:
You should create the rounded border like this:
txtfEmail.layer.cornerRadius = txtfEmail.height / 2
txtfEmail.layer.borderWidth = 1
txtfEmail.layer.borderColor = UIColor.black.cgColor
Looks like the border is set to the view under the text field
try this it works for me
open storyboard
drag and drop textfield
ctrl click and create an outlet in view controller and name it lets say emailfield
then write this code
#iboultet weak var emailfield: uitextfield!{
didset{
emailfield.layer.masktobounds = true
emailfield.layer.cornerradius = 26
emailfield.layer.borderwidth = 1
emailfield.backgroundcolor = whatever color you want

Resources