UITextField rightView moving outside of UITextFiled - ios

I'm trying to use the rightView property of the UITextField class to provide a suffix to a text field. It all seems to work correctly, until I stop editing the text field, at which point the label is moved outside of the UITextField. The code used is:
class TextFieldWithSuffix: UITextField {
var suffix: String? {
didSet {
let value = self.suffix ?? ""
let label = UILabel(frame: CGRectZero)
label.font = self.font
label.text = value
self.rightView = label
self.rightViewMode = .Always
}
}
override func rightViewRectForBounds(bounds: CGRect) -> CGRect {
var rightViewRect = super.rightViewRectForBounds(bounds)
if let suffix = self.suffix {
let suffixSize = NSString(string: suffix).sizeWithAttributes([NSFontAttributeName: self.font])
rightViewRect.size = suffixSize
}
return rightViewRect
}
}
When the view is first loaded the view looks like below:
However, when the text field has been edited and then the keyboard dismissed, it looked like below:
This has been tested on iOS 7 and 8 and both seem to be doing the same.

Turns out the issue was setting the rightView property again, which was happening when the editing finished. I found the following version fixed the issue:
class TextFieldWithSuffix: UITextField {
var suffix: String? {
didSet {
let value = self.suffix ?? ""
let suffixLabel = UILabel(frame: CGRectZero)
suffixLabel.font = self.font
suffixLabel.updateFontStyle()
suffixLabel.text = value
self.rightView = nil
self.rightView = suffixLabel
self.rightViewMode = .Always
self.setNeedsLayout()
self.layoutIfNeeded()
}
}
override func rightViewRectForBounds(bounds: CGRect) -> CGRect {
var rightViewRect = super.rightViewRectForBounds(bounds)
if let suffix = self.suffix {
let suffixSize = NSString(string: suffix).sizeWithAttributes([NSFontAttributeName: self.font])
rightViewRect.size = suffixSize
}
return rightViewRect
}
}
Hopefully this'll help someone. If you're doing this a lot you should just update the value of the label and call the setNeedsLayout() and layoutIfNeeded(), but this works so I'm just leaving it as-is.

Related

Is there any way to change text layout begin position in iOS with core text?

I have a string :
12345678901234567890123456789012345678901234567890(...)
the default label will be layouting from left to right.
and I want to display this label with this kind of layout (and the will be no ellipsis):
the text layout begin not in the left , but starts in the middle
then the layout continue to the right
fill the left at last
How to do this
You can do this a few different ways...
two "normal" UILabel as subviews
CoreText
using two CATextLayer
Here's an example using CATextLayer. Based on your descriptions:
text begins at horizontal center, then "wraps" around to the left
no ellipses truncation
uses intrinsic size of the text/string
I'm calling the custom view subclass RightLeftLabelView:
class RightLeftLabelView: UIView {
public var text: String = ""
{
didSet {
setNeedsLayout()
invalidateIntrinsicContentSize()
}
}
public var font: UIFont = .systemFont(ofSize: 17.0)
{
didSet {
setNeedsLayout()
invalidateIntrinsicContentSize()
}
}
public var textColor: UIColor = .black
{
didSet {
setNeedsLayout()
}
}
private let leftTL = CATextLayer()
private let rightTL = CATextLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
clipsToBounds = true
[leftTL, rightTL].forEach { tl in
tl.contentsScale = UIScreen.main.scale
layer.addSublayer(tl)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// get the size of the text, limited to a single line
let sz = font.sizeOfString(string: text, constrainedToWidth: .greatestFiniteMagnitude)
var r = CGRect(origin: .zero, size: CGSize(width: ceil(sz.width), height: ceil(sz.height)))
// start right text layer at horizontal center
r.origin.x = bounds.midX
rightTL.frame = r //.offsetBy(dx: r.width * 0.5, dy: 0.0)
// end left text layer at horizontal center
r.origin.x -= r.width
leftTL.frame = r //.offsetBy(dx: -r.width * 0.5, dy: 0.0)
[leftTL, rightTL].forEach { tl in
tl.string = text
tl.font = font
tl.fontSize = font.pointSize
tl.foregroundColor = textColor.cgColor
}
}
override var intrinsicContentSize: CGSize {
return font.sizeOfString(string: text, constrainedToWidth: .greatestFiniteMagnitude)
}
}
It uses this UIFont extension to get the size of the text:
extension UIFont {
func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
let attributes = [NSAttributedString.Key.font:self]
let attString = NSAttributedString(string: string,attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attString)
return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0,length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
}
}
and an example controller:
class RightLeftLabelVC: UIViewController {
let sampleStrings: [String] = [
"0123456789",
"This is a good test of custom wrapping.",
"This is a good test of custom wrapping when the text is too long to fit.",
]
var sampleIDX: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let stack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .center
v.spacing = 2
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
view.addSubview(stack)
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// center the stack view
stack.centerYAnchor.constraint(equalTo: safeG.centerYAnchor),
// full width
stack.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
])
let myTestViewA = RightLeftLabelView()
let myTestViewB = RightLeftLabelView()
let myTestViewC = RightLeftLabelView()
let actualA = UILabel()
let actualB = UILabel()
let actualC = UILabel()
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualA)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewA)
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualB)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewB)
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualC)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewC)
// some vertical spacing
stack.setCustomSpacing(32.0, after: myTestViewA)
stack.setCustomSpacing(32.0, after: myTestViewB)
stack.setCustomSpacing(32.0, after: myTestViewC)
// for convenience
let rlViews: [RightLeftLabelView] = [
myTestViewA, myTestViewB, myTestViewC
]
let labels: [UILabel] = [
actualA, actualB, actualC
]
let strings: [String] = [
"0123456789",
"This is a good test of custom wrapping.",
"This is an example test of custom wrapping when the text is too long to fit.",
]
// set various properties
var i: Int = 0
for (v, l) in zip(rlViews, labels) {
v.backgroundColor = .cyan
v.text = strings[i]
l.backgroundColor = .green
l.text = strings[i]
l.numberOfLines = 0
i += 1
}
}
func infoLabel(_ s: String) -> UILabel {
let v = UILabel()
v.text = s
v.font = .italicSystemFont(ofSize: 14.0)
return v
}
}
The result looks like this:

Align label's center.x with image inside tabBar's imageView

I need to get a label's center.x directly aligned with the image inside a tabBar's imageView. Using the below code the label is misaligned, instead of the label's text "123" being directly over the bell inside the tabBar, it's off to the right.
guard let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return }
guard let fourthTab = tabBarController?.tabBar.items?[3].value(forKey: "view") as? UIView else { return }
guard let imageView = fourthTab.subviews.compactMap({ $0 as? UIImageView }).first else { return }
guard let imageViewRectInWindow = imageView.superview?.superview?.convert(fourthTab.frame, to: keyWindow) else { return }
let imageRect = AVMakeRect(aspectRatio: imageView.image!.size, insideRect: imageViewRectInWindow)
myLabel.text = "123"
myLabel.textAlignment = .center // I also tried .left
myLabel.center.x = imageRect.midX
myLabel.center.y = UIScreen.main.bounds.height - 74
myLabel.frame.size.width = 50
myLabel.frame.size.height = 21
print("imageViewRectInWindow: \(imageViewRectInWindow)") // (249.99999999403948, 688.0, 79.00000000298022, 48.0)
print("imageRect: \(imageRect)") // (265.4999999955296, 688.0, 48.0, 48.0)
print("myLabelRect: \(myLabel.frame)") // (289.4999999955296, 662.0, 50.0, 21.0)
It might be a layout issue, as in, setting the coordinates before everything is laid out. Where do you call the code from? I was able to get it to work with the following, but got strange results with some if the functions you use, so cut out a couple of them. Using the frame of the tabview worked for me, and calling the coordinate setting from the view controller's viewDidLayoutSubviews function.
class ViewController: UIViewController {
var myLabel: UILabel = UILabel()
var secondTab: UIView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.addSubview(myLabel)
myLabel.textColor = .black
myLabel.text = "123"
myLabel.textAlignment = .center // I also tried .left
myLabel.frame.size.width = 50
myLabel.frame.size.height = 21
secondTab = tabBarController?.tabBar.items?[1].value(forKey: "view") as? UIView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let secondTab = secondTab else {
return
}
myLabel.center.x = secondTab.frame.midX
myLabel.center.y = UIScreen.main.bounds.height - 70
}
}
Try below code to return a centreX by tabbar item index.
extension UIViewController {
func centerX(of tabItemIndex: Int) -> CGFloat? {
guard let tabBarItemCount = tabBarController?.tabBar.items?.count else { return nil }
let itemWidth = view.bounds.width / CGFloat(tabBarItemCount)
return itemWidth * CGFloat(tabItemIndex + 1) - itemWidth / 2
}
}

Vertical alignment on editable UITextView

I want to set the vertical alignment on my UITextView. The text view is editable and scroll is disabled:
let textView = UITextView(frame: frame)
textView.backgroundColor = .clear
textView.attributedText = myAttributedString
textView.isUserInteractionEnabled = true
textView.isEditable = true
textView.allowsEditingTextAttributes = true
textView.isScrollEnabled = false
textView.textContainerInset = .zero
self.addSubview(textView)
So, I would like to do something like :
textView.verticalAlignment = .center
I already tried subclassing the text view and adding an attribute like this:
class MyTextView: UITextView {
public var verticalAlignment: UIControl.ContentVerticalAlignment = .center {
didSet {
let topCorrection: CGFloat
switch verticalAlignment {
case .center:
topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0
case .bottom:
topCorrection = (bounds.size.height - contentSize.height * zoomScale)
case .top, .fill:
topCorrection = 0
#unknown default:
topCorrection = 0
}
contentInset.top = max(0, topCorrection)
}
}
}
But it seems not to work with isScrollEnabled set to false.
Every other solution I found on the internet didn't work either and I'm a bit hopeless… Can you help me?
Thanks
Maybe you intended to use a UITextField?
let field = UITextField()
field.contentVerticalAlignment = UIControlContentVerticalAlignment.center
Or set it to .bottom, .top, etc.
Create a custom textView
class CustomTextView: UITextView {
override var canBecomeFirstResponder: Bool {
return false
}
override var selectedTextRange: UITextRange? {
get {
return nil
} set {
}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
longPressGestureRecognizer.minimumPressDuration < 0.325 {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
gestureRecognizer.isEnabled = false
return false
}
}
Use this textview as a baseclass for the textview

Changing textColor with #IBInspectable inside array of Labels

I have weekDaysContainerView subclassing UIView, and trying to change textColor with #IBInspectable. Interface Builder show options to change color inside of it, but it never does in real app.
var weekdays = [UILabel]()
#IBInspectable var dayLabelColor: UIColor = UIColor.gray {
didSet {
for day in weekdays {
day.textColor = dayLabelColor
}
}
}
lazy var dayLabelContainerView : UIView = {
let formatter = DateFormatter()
let view = UIView()
for index in 1...7 {
let day: NSString = formatter.weekdaySymbols[index % 7 ] as NSString
let weekdayLabel = UILabel()
weekdayLabel.text = day.substring(to: 3).uppercased()
weekdayLabel.textColor = UIColor.gray
weekdayLabel.textAlignment = NSTextAlignment.center
view.addSubview(weekdayLabel)
weekdays.append(weekdayLabel)
}
addSubview(view)
return view
}()

Issue with "if" statement: "trying to style message.content differently according to message.sender

I have created a class of message including, content and sender. I successfully store the desired data in Parse and I am querying them. So far, no problem. Then, I attempt to filter the messages according to the sender, or the receiver, in order to display them in different manners on my tableView.
For instance, let's say that is sender is currentUser, the text is blue, otherwise it is green.
If currentUser sends a message, all the text becomes blue, even the one sent by the other user; and vice versa.
class Message {
var sender = ""
var message = ""
var time = NSDate()
init(sender: String, message: String, time: NSDate)
{
self.sender = sender
self.message = message
self.time = time
}
}
var message1: Message = Message(sender: "", message: "", time: NSDate())
func styleTextView()
{
if message1.sender == PFUser.currentUser()?.username {
self.textView.textColor = UIColor.blueColor()
} else {
self.textView.textColor = UIColor.greenColor()
}
}
func addMessageToTextView(message1: Message)
{
textView.text = message1.message
self.styleTextView()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell: messageCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! messageCell
let message = self.array[indexPath.row]
cell.setupWithMessage(message)
return cell
}
I have other codes on my viewController, however I believe they are irrelevant to this matter; if needed, I can provide them.
Any idea why I cannot have the textViews in different style according to the sender?
They are all either blue, either green.
Below is the code for the full set up of the tableViewCell:
class messageCell: UITableViewCell {
private let padding: CGFloat = 10.0
var array1 = [Message]()
private func styleTextView()
{
let halfTextViewWidth = CGRectGetWidth(textView.bounds) / 2.0
let targetX = halfTextViewWidth + padding
let halfTextViewHeight = CGRectGetHeight(textView.bounds) / 2.0
self.textView.font = UIFont.systemFontOfSize(12.0)
if PFUser.currentUser()?.username == message1.sender && textView.text == message1.message {
self.textView.backgroundColor = UIColor.blueColor()
self.textView.layer.borderColor = UIColor.blueColor().CGColor
self.textView.textColor = UIColor.whiteColor()
textView.center.x = targetX
textView.center.y = halfTextViewHeight
} else {
self.textView.backgroundColor = UIColor.orangeColor()
self.textView.layer.borderColor = UIColor.orangeColor().CGColor
self.textView.textColor = UIColor.whiteColor()
self.textView.center.x = CGRectGetWidth(self.bounds) - targetX
self.textView.center.y = halfTextViewHeight
}
}
private lazy var textView: MessageBubbleTextView = {
let textView = MessageBubbleTextView(frame: CGRectZero, textContainer: nil)
self.contentView.addSubview(textView)
return textView
}()
class MessageBubbleTextView: UITextView
{
override init(frame: CGRect = CGRectZero, textContainer: NSTextContainer? = nil)
{
super.init(frame: frame, textContainer: textContainer)
self.scrollEnabled = false
self.editable = false
self.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
self.layer.cornerRadius = 15
self.layer.borderWidth = 2.0
}
required init(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
private let minimumHeight: CGFloat = 30.0
private var size = CGSizeZero
private var maxSize: CGSize {
get {
let maxWidth = CGRectGetWidth(self.bounds)
let maxHeight = CGFloat.max
return CGSize(width: maxWidth, height: maxHeight)
}
}
func setupWithMessage(message: Message) -> CGSize
{
textView.text = message.message
size = textView.sizeThatFits(maxSize)
if size.height < minimumHeight {
size.height = minimumHeight
}
textView.bounds.size = size
self.styleTextView()
return size
}
}
One possible error can be in this line:
if message1.sender == PFUser.currentUser()?.username
where you are comparing different types since message1 is a String whereas PFUser.currentUser()?.username is an optional String. The right way to compare them is:
if let username = PFUser.currentUser()?.username where username == message1.sender {
self.textView.textColor = UIColor.blueColor()
} else {
self.textView.textColor = UIColor.greenColor()
}
This is because you are changing the text color of the entire UITextView with your code. See the following post for a start in the right direction: Is it possible to change the color of a single word in UITextView and UITextField

Resources