Swift Dynamically created UILabel shows up twice - ios

I have written a custom graph UIView subclass, and I use it to graph some basic data, and insert some user-defined data. As a final step, I'd like to add a UILabel on top of the graph with the user-defined data-point called out.
I highlight the point, and then create and add the UILabel:
if(graphPoints[i] == highlightPoint){
var point2 = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point2.x -= 8.0/2
point2.y -= 8.0/2
let circle2 = UIBezierPath(ovalInRect:
CGRect(origin: point2,
size: CGSize(width: 8.0, height: 8.0)))
highlightColor.setFill()
highlightColor.setStroke()
circle2.fill()
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
var pointLabel : UILabel = UILabel()
pointLabel.text = "Point = \(graphPoints[i])"
pointLabel.frame = CGRectMake(point2.x, point2.y, 100, 50)
self.addSubview(pointLabel)
} else {
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
}
This looks like it should work, but the UILabel is added twice. What am I doing wrong?

This code is probably in drawRect. You're doing subview-adding in drawRect which is incorrect. drawRect gets called at least twice as the view appears, and perhaps many times after that.
You should be overriding some early lifecycle method, like awakeFromNib() or the constructor. In that, construct your label and add it as a child view. This way the addSubView() happens once as it should. The label will be invisible having no contents, so don't worry about that.
In drawRect, just update all the necessary attributes of the label including the frame.
If you find you're actually needing lots of text "labels" that come and go, different quantity for every graph, you don't really want UILabels as subviews after all. Consider directly drawing text on screen with someString.drawAtPoint(...)

Related

Text not vertically centered in UILabel for some fonts

I want the text to always be vertically centered. It doesn't work for some specific fonts such as "DamascusSemiBold", What is the solution?
let label = VerticalLabel(frame: CGRect(x: 10, y: 100, width: 300, height: 50))
label.layer.borderColor = UIColor.red.cgColor
label.layer.borderWidth = 1
label.text = "Text Sticker"
label.font = UIFont(name: "DamascusSemiBold", size: 30)
label.numberOfLines = 0
label.textColor = .white
label.textAlignment = .center
self.view.addSubview(label)
public override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
var textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
print(textRect, bounds)
textRect.origin.y = bounds.origin.y + (bounds.size.height - textRect.size.height) * 0.5
print(textRect)
return textRect
}
public override func drawText(in rect: CGRect) {
let actualRect = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
super.drawText(in: actualRect)
}
It's not centered, just top!
I know there are a lot of threads similar to this, but I've read all of them (as far as I can tell) and haven't seen the same problem I'm having.
I'm trying to get all of the text to show in a label VERTICALLY.
Let me explain - Fonts are not always created the same way, so while their total height may be the same (24pts, for example). However, the Ascender and Descender vary widely - one font may be mostly above the baseline, while another is mostly below. Therefore, the same text, with different fonts may not always show in the same view/label.
The first is Helvetica-Bold 300pts. The second is Apple Gothic 300pts.
Notice how the bottom of the "g" is cut off with Helvetica (and many other fonts too - try it, you'll see).
So my issue is this: I'd like to be able to see the entire text, regardless of the font. If the text in the "Helvetica" example could be moved up (and centered) within the label, it would solve my problem. To make it easier, I only need to display a single line.
Unfortunately, none of the solutions I've seen involve the descenders and ascenders of the font and figuring out how to draw the text within a Rect and not have it cropped. Note that the "VerticalAlignment" solutions in various threads don't fix this particular problem.
I calculate boundRect for attributedString and update the frame of label, and center text vertically
super.drawText(in: rect.inset(by: UIEdgeInsets(top: -(font.ascender - font.capHeight), left: 0, bottom: font.descender, right: 0)))
Above code cuts descender even though center vertically, how can I center text vertically without cutting of ascender, descender?
Screenshot
Does anyone have any ideas or solutions for this?
f you have a label with longer text that will make more than one line, set numberOfLines to 0 (zero here means an unlimited number of lines).
label.numberOfLines = 0

Add label to point

I have followed this tutorial: https://www.raywenderlich.com/410-core-graphics-tutorial-part-2-gradients-and-contexts
I am trying to add a label over each point with the Int for the point, but it wont show. Adding code in the //Draw the circles on top of graph stroke
//Draw the circles on top of graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point.x -= Constants.circleDiameter / 2
point.y -= Constants.circleDiameter / 2
let circle = UIBezierPath(ovalIn: CGRect(origin: point, size: CGSize(width: Constants.circleDiameter, height: Constants.circleDiameter)))
circle.fill()
//let label = UILabel(frame: CGRect(origin: point, size: CGSize(width: Constants.circleDiameter, height: Constants.circleDiameter)))
let label = UILabel()
label.frame.origin = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
label.text = "TDDDDDDDE"
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
self.addSubview(label)
self.view.addSubview(label)
}
Can anyone help me with adding the label? The label wont show, and I want it over the points.
Best guests, given there's not the full context. I see 3 possible reasons
1: You should replace
self.addSubview(label)
self.view.addSubview(label)
By
self.addSubview(label)
The second line will remove the label from view to add it to self.view, which might be a problem if they are different.
2: With the following line, you're telling the label to use autolayout ...but do not provide any constraint, so they might be there but not where you expected them to be.
label.translatesAutoresizingMaskIntoConstraints = false
To add constraints: see official Apple Doc, example below:
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: aConstant).isActive = true
But 3: your label is initialised without a frame/an empty frame. Call sizeToFit on your label after having set the content, fonts, etc if you don't use autolayout. If you stick to autolayout, this point is not applicable.
And another point indirectly linked to your question: If you manage to get your code to work, you'll probably have too much labels: your code seems to be in the draw method, which means that you'll be instantiating new labels each time the view is rendered ...and the previous ones won't be removed. A better practice would be instanciate the labels when you load the data (if they depend on the data points), to position them in layoutSubviews or to skip the labels entirely and use NSAttributedString draw functions to render the text directly in the view.

StackView - center buttons with text below image

I am using this class for center text below icon for UIButton:
#IBDesignable
class TopIconButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
let kTextTopPadding:CGFloat = 3.0;
var titleLabelFrame = self.titleLabel!.frame;
let labelSize = titleLabel!.sizeThatFits(CGSize(width: self.contentRect(forBounds: self.bounds).width, height: CGFloat.greatestFiniteMagnitude))
var imageFrame = self.imageView!.frame;
let fitBoxSize = CGSize(width: max(imageFrame.size.width, labelSize.width), height: labelSize.height + kTextTopPadding + imageFrame.size.height)
let fitBoxRect = self.bounds.insetBy(dx: (self.bounds.size.width - fitBoxSize.width)/2, dy: (self.bounds.size.height - fitBoxSize.height) / 2)
imageFrame.origin.y = fitBoxRect.origin.y
imageFrame.origin.x = fitBoxRect.midX - (imageFrame.size.width/2)
self.imageView!.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.width
titleLabelFrame.size.height = labelSize.height
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2)
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding
self.titleLabel!.frame = titleLabelFrame
self.titleLabel!.textAlignment = .center
}
}
It's working fine. My problem is when I start using in UIStackView. I am creating something similar to UITabBar so I have 5 buttons next to each other. I want to middle button be in exact center of screen but it's always several pixels to side. I check my constraints for UIStackView and it's okay. I remove all buttons exect middle one and then it's in exact middle. I tried different options for UIStackView (Fill, Centered, Fill Equally, etc.). Nothing helps. Do anyone has any idea? Each button has different icon (with different size) and texts are different too (free, premium, etc. - sometimes bigger then icon sometimes smaller), is this relevant?
Example of my StackView (red line shows where is middle):
The image size is going to be relevant here. Once you get all 5 images to be the same exact size you won't experience this issue anymore. Using 'fill equally' after doing that and you will get the result you want

iOS - UITextField: extend the bottom line for ANY resolution

I created a UITextFiled with a bottom line using this:
let Bottomline CALayer = ()
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
TextField.borderStyle = UITextBorderStyle.none
TextField.layer.addSublayer (Bottomline)
and the result of an iPhone 6 (right) is this:
Ok.
✄------------------------
The problem is to run the same application on a Pro iPad, because the
bottom line does not extend following the UITextField, but is shorter
This is the result on iPad Pro:
I do not understand why the bottom line does not follow the UITextField. When I called the bottom line I defined as:
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
I have specified that the length of the line at the bottom must be:
width: usernameTextField.frame.width
What's the problem?
EDIT 1: The contrains are correct, because the UITextField adapts to
all types of resolution
EDIT:2 Thanks Matt! Now work!!!
I do not understand why the bottom line does not follow the UITextField
Because it's a layer. Layers do not automatically change size when their superlayer (the text field) changes size.
So, you need to redraw the bottom line every time the text field changes size.
At the moment, though, you are configuring the "bottom line" layer in viewDidLoad. So you are basing it on the frame that the text field has at that moment. But the text field has not yet attained its real size. Then it does change size, and meanwhile your "bottom line" layer just sits there — so now it is the wrong size.
An easy solution is to subclass UITextField and redraw the line every time layoutSubviews is called:
class MyTextField : UITextField {
var lineLayer : CALayer?
override func layoutSubviews() {
super.layoutSubviews()
self.lineLayer?.removeFromSuperlayer()
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.bounds.height-7, width: self.bounds.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(bottomLine)
self.lineLayer = bottomLine
}
}
If your text field is a MyTextField, it will behave exactly as you desire.

How do you add an outline to text programmatically?

I have two apps, one with a UILabel and one using SpriteKit with a SKLabelNode. I'd like to add a black outline around the white text.
I can't find any outline or border properties or anything like that within swift. I've tried just creating new labels with slightly bigger, black font behind them but that didn't look right at all.
Here is my game with SpriteKit
title.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
title.text = "Tap to start!"
title.fontName = "Arial"
title.zPosition = 10
title.fontSize = 50
self.addChild(title)
And here is my other one that uses UILables (It's within a red rectangle so it's easier to see)
let rectangle = UIView(frame:CGRect(x: self.view.frame.size.width / 2 - 150, y: self.view.frame.size.height / 2 - 75, width: 300, height: 150))
rectangle.backgroundColor = UIColor.red
self.view.addSubview(rectangle)
let label = UILabel(frame: CGRect(x: rectangle.frame.size.width / 2 - 75, y: rectangle.frame.size.height / 2 - 10, width: 150, height: 20))
label.textAlignment = .center
label.text = "Tap to Start!"
label.textColor = UIColor.white
label.font = UIFont(name: "Arial", size: 20)
rectangle.addSubview(label)
How do I outline these labels?
For SKLabelNode's, there is no 'easy' way of outlining text. There is however a 'hack'. It involves adding 8 additional duplicate nodes around the label that are coloured black (or whatever colour you want the outline to be), have a zPosition less than the original and otherwise, be identical.
It's important that each label has the same font size as the original.
Your attempt at doing this involved increasing the font size of your one outline copy label. As you said in the question, it doesn't work. You have to use multiple labels so the effect appears seamlessly.
You would place one copy directly above the original, one directly below, one to the left, one to the right, then one on each corner (top right, top left, bottom right, and bottom left). This gives the effect of an outline, however is not efficient and should be taken with a grain of salt.
Note: Keep the amount shifted from the original consistent for all the copies, so the 'outline' is evenly sized.
This should work for UILabels too, however I think there might be an easier way to do this with UILabels.

Resources