Simple function for a customised label - swift

I'm trying to write a most basic function that will set the parameters of a label.
First I set the position where I want to put a label:
var xCircularPos = UIScrollView.frame.width / 2
var yCircularPos = UIScrollView.frame.height * 0.15
And than write a function:
func textParameters(labelName: UILabel, text: String, xPosition: CGFloat, yPosition: CGFloat) {
labelName.textAlignment = .center
labelName.text = text
labelName.sizeToFit()
labelName.layer.position.x = CGFloat(0)
labelName.layer.position.y = CGFloat(0)
UIScrollView.addSubview(labelName)
}
Than I create a label and use a function:
let scoreLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 21))
textParameters(labelName: scoreLabel, text: "Счет", xPosition: xCIrcularPos, yPosition: yCIrcularPos)
But nothing happens. When I set the same parameters for the label without a function - everything works fine. In both cases everything happens in viewDidLoad.
Can you please give a tip what I am missing?

Related

How do I Fade Label out at end instead of replacing end with "..." if it's too long / How to use GoogleToolboxForMac

I've found a solution with GTMFadeTruncatingLabelTest from GoogleToolboxForMac but don't really understand how to use it and I don't find anything about it
but if you have another solution besides of this one
If you can't help me with GoogleToolboxForMac feel free to suggest other solution
Should kinda look like this at the end:
I am not sure about the GTMFadeTruncatingLabelTest but I can offer an alternative solution.
Steps
Check if the label's text is going to be truncated
If 1 is true, Create a CAGradientLayer that goes from Opaque to Transparent
Apply the gradient layer as a mask to the UILabel
Implementation
If you don't want to read the rest, just grab the code from this repo
I wrapped step 1, 2 and 3 from above in a custom UILabel subclass. The reasoning is explained in the comments.
class FadingLabel: UILabel
{
// Add a property observer for text changes
// as we might not need to fade anymore
override var text: String?
{
didSet
{
// Check if the text needs to be faded
fadeTailIfRequired()
}
}
// Add a property observer for numberOfLines changes
// as only 1 line labels are supported for now
override var numberOfLines: Int
{
didSet
{
// Reset the number of lines to 1
if numberOfLines != 1
{
numberOfLines = 1
}
}
}
override func layoutSubviews()
{
super.layoutSubviews()
// The label's frame might have changed so check
// if the text needs to be faded or not
fadeTailIfRequired()
}
/// The function that handles fading the tail end of the text if the text goes
/// beyond the bounds of the label's width
private func fadeTailIfRequired()
{
// Reset the numberOfLines to 1
numberOfLines = 1
// Prevent processing fading when the library is in the middle of
// processing the string to truncate the ellipsis
if !isTruncatingEllipsis
{
// Check if the label's has it's width set and if the text goes
// beyond it's width plus a margin of safety
if bounds.width > CGFloat.zero && intrinsicContentSize.width > bounds.width + 5
{
// Fade label works better with this setting
allowsDefaultTighteningForTruncation = true
// Initialize and configure a gradient to start at the end of
// the label
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
gradient.startPoint = CGPoint(x: 0.8, y: 0.5)
gradient.endPoint = CGPoint(x: 0.99, y: 0.5)
// Apply the gradient as a mask to the UILabel
layer.mask = gradient
// Remove ellipsis added as the default UILabel truncation character
removeEllipsis()
// We do not need to go beyond this point
return
}
// If the text has not been truncated, remove the gradient mask
if originalText == text
{
// Remove the layer mask
layer.mask = nil
}
}
}
/// Keep removing 1 character from the label till it no longer needs to truncate
private func removeEllipsis()
{
isTruncatingEllipsis = true
// Keep looping till we do not have the perfect string length
// to fit into the label
while intrinsicContentSize.width > bounds.width
{
// Drop the last character
text = String(text!.dropLast())
}
isTruncatingEllipsis = false
}
}
Then you can use it like a regular UILabel, for example:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let fadingLabelWithLongText = FadingLabel()
view.addSubview(fadingLabelWithLongText)
fadingLabelWithLongText.text = "Fading label with text more than it's bounds can handle"
fadingLabelWithLongText.textColor = .white
fadingLabelWithLongText.frame = CGRect(x: 20, y: 90, width: 250, height: 50)
let regularLabelWithLongText = UILabel()
view.addSubview(regularLabelWithLongText)
regularLabelWithLongText.text = "Regular label with text more than it's bounds can handle"
regularLabelWithLongText.textColor = .white
regularLabelWithLongText.frame = CGRect(x: 20, y: 160, width: 250, height: 50)
let fadingLabelWithShortText = UILabel()
view.addSubview(fadingLabelWithShortText)
fadingLabelWithShortText.text = "Fading label with text that fits"
fadingLabelWithShortText.textColor = .white
fadingLabelWithShortText.frame = CGRect(x: 20, y: 230, width: 250, height: 50)
let regularLabelWithShortText = UILabel()
view.addSubview(regularLabelWithShortText)
regularLabelWithShortText.text = "Regular label with text that fits"
regularLabelWithShortText.textColor = .white
regularLabelWithShortText.frame = CGRect(x: 20, y: 300, width: 250, height: 50)
}
Output
Limitation
This way only supports single line UILabels
Update
Added a function to remove the default truncation method of using ellipsis (three dots) by UILabel with this function.
/// Keep removing 1 character from the label till it no longer needs to truncate
private func removeEllipsis()
{
isTruncatingEllipsis = true
// Keep looping till we do not have the perfect string length
// to fit into the label
while intrinsicContentSize.width > bounds.width
{
// Drop the last character
text = String(text!.dropLast())
}
isTruncatingEllipsis = false
}
This function has been updated in the original code and repo mentioned above.
I Think the easiest way is to use the following code:
titleLabel.adjustsFontSizeToFitWidth = false
titleLabel.lineBreakMode = .byClipping
It automatically fades the last words if applicable and text size has more width than UILabel!
thanks to thi

Swift: Center NSAttributedString in View horizontally and vertically

Hi there,
I have a class CardButton: UIButton . In the draw method of this CardButton, I would like to add and center(vertically and horizontally) NSAttributed String, which is basically just one Emoji, inside of it. The result would look something like this:
However, NSAttributedString can be only aligned to center in horizontal dimension inside the container.
My idea for solution:
create a containerView inside of CardButton
center containerView both vertically and horizontally in it's container(which is CardButton)
add NSAttributedString inside the containerView and size containerView to fit the string's font.
So the result would look something like this:
My attempt for this to happen looks like this:
class CardButton: UIButton {
override func draw(){
//succesfully drawing the CardButton
let stringToDraw = attributedString(symbol, fontSize: symbolFontSize) //custom method to create attributed string
let containerView = UIView()
containerView.backgroundColor = //green
addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
let centerXConstraint = containerView.centerXAnchor.constraint(equalTo: (self.centerXAnchor)).isActive = true
let centerYConstraint = containerView.centerYAnchor.constraint(equalTo: (self.centerYAnchor)).isActive = true
stringToDraw.draw(in: containerView.bounds)
containerView.sizeToFit()
}
}
Long story short, I failed terribly. I first tried to add containerView to cardButton, made the background green, gave it fixed width and height jut to make sure that it got properly added as a subview. It did. But once I try to active constraints on it, it totally disappears.
Any idea how to approach this?
There is always more than one way to achieve any particular UI design goal, but the procedure below is relatively simple and has been adapted to suit the requirements as presented and understood in your question.
The UIButton.setImage method allows an image to be assigned to a UIButton without the need for creating a container explicitly.
The UIGraphicsImageRenderer method allows an image to be made from various components including NSAttributedText, and a host of custom shapes.
The process utilising these two tools to provide the rudiments for your project will be to:
Render an image with the appropriate components & size
Assign the rendered image to the button
A class could be created for this functionality, but that has not been explored here.
Additionally, your question mentions that when applying constraints the content disappears. This effect can be observed when image dimensions are too large for the container, constraints are positioning the content out of view and possibly a raft of other conditions.
The following code produces the above image:
func drawRectangleWithEmoji() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 512, height: 512))
let img = renderer.image { (ctx) in
// Create the outer square:
var rectangle = CGRect(x: 0, y: 0, width: 512, height: 512).insetBy(dx: 7.5, dy: 7.5)
var roundedRect = UIBezierPath(roundedRect: rectangle, cornerRadius: 50).cgPath
// MARK: .cgPath creates a CG representation of the path
ctx.cgContext.setFillColor(UIColor.white.cgColor)
ctx.cgContext.setStrokeColor(UIColor.blue.cgColor)
ctx.cgContext.setLineWidth(15)
ctx.cgContext.addPath(roundedRect)
ctx.cgContext.drawPath(using: .fillStroke)
// Create the inner square:
rectangle = CGRect(x: 0, y: 0, width: 512, height: 512).insetBy(dx: 180, dy: 180)
roundedRect = UIBezierPath(roundedRect: rectangle, cornerRadius: 10).cgPath
ctx.cgContext.setFillColor(UIColor.green.cgColor)
ctx.cgContext.setStrokeColor(UIColor.green.cgColor)
ctx.cgContext.setLineWidth(15)
ctx.cgContext.addPath(roundedRect)
ctx.cgContext.drawPath(using: .fillStroke)
// Add emoji:
var fontSize: CGFloat = 144
var attrs: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: fontSize)//,
//.backgroundColor: UIColor.gray //uncomment to see emoji background bounds
]
var string = "❤️"
var attributedString = NSAttributedString(string: string, attributes: attrs)
let strWidth = attributedString.size().width
let strHeight = attributedString.size().height
attributedString.draw(at: CGPoint(x: 256 - strWidth / 2, y: 256 - strHeight / 2))
// Add NSAttributedString:
fontSize = 56
attrs = [
.font: UIFont.systemFont(ofSize: fontSize),
.foregroundColor: UIColor.brown
]
string = "NSAttributedString"
attributedString = NSAttributedString(string: string, attributes: attrs)
let textWidth = attributedString.size().width
let textHeight = attributedString.size().height
attributedString.draw(at: CGPoint(x: 256 - textWidth / 2, y: 384 - textHeight / 2))
}
return img
}
Activate the NSLayoutContraints and then the new image can be set for the button:
override func viewDidLoad() {
super.viewDidLoad()
view = UIView()
view.backgroundColor = .white
let buttonsView = UIView()
buttonsView.translatesAutoresizingMaskIntoConstraints = false
// buttonsView.backgroundColor = UIColor.gray
view.addSubview(buttonsView)
NSLayoutConstraint.activate([
buttonsView.widthAnchor.constraint(equalToConstant: CGFloat(buttonWidth)),
buttonsView.heightAnchor.constraint(equalToConstant: CGFloat(buttonWidth)),
buttonsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
buttonsView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
let cardButton = UIButton(type: .custom)
cardButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
cardButton.setImage(drawRectangleWithEmoji(), for: .normal)
let frame = CGRect(x: 0, y: 0, width: buttonWidth, height: buttonWidth)
cardButton.frame = frame
buttonsView.addSubview(cardButton)
}
Your comments will be appreciated as would constructive review of the code provided.

Multiple UILabel in Page Control

I am trying to do a scrollView + pageControl for multiple UILabel i.e. each view should have 2 UILabel - one for title and one for descriptions.
I am familiar with adding a single UILabel into scrollView + pageControl. The question is how do I add more than one UILabel into the scrollView's subview? I have the following code which works fine for a single UILabel:
#IBOutlet weak var bloggerReviewScrollView: UIScrollView!
#IBOutlet weak var bloggerReviewPageControl: UIPageControl!
var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
var reviews: [Reviews] = [Reviews(name: "NameA", description: "DescriptionA", date: Date.init(), url: nil), Reviews(name: "NameB", description: "DescriptionB", date: Date.init(), url: nil)]
func updateReview() {
reviewPageControl.numberOfPages = reviews.count
for index in 0..<reviews.count {
frame.origin.x = reviewScrollView.frame.size.width * CGFloat(index)
frame.size = reviewScrollView.frame.size
let reviewContent = UILabel(frame: frame)
reviewContent.text = reviews[index].description
reviewContent.numberOfLines = 0
reviewScrollView.addSubview(reviewContent)
}
reviewScrollView.contentSize = CGSize(width: reviewScrollView.frame.size.width * CGFloat(reviews.count), height: reviewScrollView.frame.size.height)
reviewScrollView.delegate = self
}
However, other than reviews[index].description, I also want to display reviews[index].name in a different label so that the style for that label is independent of the style of the description label in the view. Are there any ways for me to do so?
Thanks a bunch!
What you need is to decide where and how you want to display your both labels, meaning label frames, I have divided into two parts you can change the frame based on your requirement and create new Label instance and add that to your reviewScrollView
func updateReview() {
reviewPageControl.numberOfPages = reviews.count
for index in 0..<reviews.count {
frame.origin.x = reviewScrollView.frame.size.width * CGFloat(index)
frame.size = reviewScrollView.frame.size
//title lable
////set title frame based on your requirment
let reviewLabelFrame = CGRect(x: frame.origin.x, y: 0, width: frame.width, height: frame.height/2)
let titleLabel = UILabel(frame: reviewLabelFrame)
titleLabel.text = reviews[index]. name // get name and set the title
titleLabel.numberOfLines = 0
reviewScrollView.addSubview(titleLabel)
//descriptions Label
//set descriptions frame based on your requirment
let desLableFram = CGRect(x: frame.origin.x, y: frame.height/2, width: frame.width, height: frame.height/2)
let desLable = UILabel(frame: desLableFram)
desLable.text = reviews[index].description //get description and set
desLable.numberOfLines = 0
reviewScrollView.addSubview(desLable) //add your descriptions
}
you need to calculate the frames for both the label based on your requirement

unable to find view with tag

In my viewWillAppear() I create a label and give it a tag. When another condition is met, I try to remove the label, but for some reason, that is not working and the label is still in the view. I must be doing something wrong...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
var label: UILabel?
// Add label if there are no recipes
if (recipeBook.recipesArr.count == 0) {
label = self.view.viewWithTag(123) as? UILabel
//label?.tag = 123 // arbitrary num
label = UILabel(frame: CGRect(x: 0, y: self.view.frame.height/3, width: self.view.frame.width, height: 100))
label?.text = "Add A Recipe"
label?.textColor = UIColor(red:0.93, green:0.92, blue:0.92, alpha:1.0)
label?.font = label?.font.withSize(36)
label?.textAlignment = .center
self.view.addSubview(label!)
}
else {
// remove it
if let foundLabel = self.view.viewWithTag(123) {
foundLabel.removeFromSuperview()
} else {
print("Couldn't find label with tag in view")
}
}
}
I didn't realize in this line label = UILabel(frame: CGRect(x: 0, y: self.view.frame.height/3, width: self.view.frame.width, height: 100)) I was creating a new label which has a default tag of 0. Changed it to label?.frame = CGRect(x: 0, y: self.view.frame.height/3, width: self.view.frame.width, height: 100) so that I'm not creating a new label and everything is working fine. Silly mistake.
Your code is not doing what you think it is. If your recipesArr is empty (or more accurately the count is zero) you are trying to find a label/view with the tag 123. That is then ignored and you create a new label but don't give it a tag.
What you need to do is assign the label you create the tag 123 after you create it like this:
label?.tag = 123
Then you will have created the label and set it's tag so it can then subsequently be found.
you are recreating the label after the line label = UILabel(frame: CGRect
you can create the label programmatically like so without optionals:
lazy var recipeLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: self.view.frame.height/3, width: self.view.frame.width, height: 100))
label.tag = 123
label.text = "Add A Recipe"
label.font = UIFont.systemFont(ofSize: 17.0)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
then add it to your subview with iOS 9+ constraints if needed:
self.view.addSubview(recipeLabel)
then you can access the view anywhere by simply referencing the view recipeLabel or by the .tag property if you desire.

Stacked progress indicator swift

How can I create a stacked progress indicator to use with the interface builder like this one from the storage management?
Is there any similar cocoa control?
If there isn't, should I try with creating a custom view, in witch I add a new view inside every time i call a method like progress.add(width: 10.0, color: NSColor(...))?
I just made a function to draw some views, you can play with it a bit.
func drawStackedProgress(percentages:[Float], width:Float, height:Float, x:Float, y:Float){
var currentX = x
// I just threw a bunch of random (mostly probably ugly) colors in this array. Go ahead and make sure there's as many colors as stacked elements.
var colors = [UIColor.red, UIColor.blue, UIColor.green, UIColor.brown, UIColor.cyan]
var index = -1
for percentage in percentages{
index += 1
let DynamicView=UIView(frame: CGRect(x: CGFloat(currentX), y: CGFloat(y), width: CGFloat(Double(percentage)*Double(width)), height: CGFloat(height)))
currentX+=Float(Double(percentages[index])*Double(width))
DynamicView.backgroundColor=colors[index]
self.view.addSubview(DynamicView)
}
}
Implementation:
drawStackedProgress(percentages: [0.1,0.3,0.5,0.1], width: 200, height: 20, x: 200, y: 200)
Update: Translated code for a mac application.
Corrected index out of range for colors.
public func drawStackedProgress(percentages: [CGFloat], width: CGFloat, height: CGFloat, x: CGFloat, y: CGFloat){
var currentX = x
var colors = [NSColor.red, NSColor.blue, NSColor.green, NSColor.brown, NSColor.cyan]
var index = -1
for percentage in percentages{
index += 1
let DynamicView = NSView(frame: CGRect(x: currentX, y: y, width: percentage * width, height: height))
currentX += percentages[index] * width
// if the colors have been all used, it starts choosing again from the first
DynamicView.layer?.backgroundColor = colors[index % colors.count].cgColor
self.addSubview(DynamicView)
}
}
I've made this simple pod, you can check it: https://github.com/adriatikgashi/MultipleProgressBar
private lazy var multipleProgressView: MultiProgressView = {
let view = MultiProgressView()
return view
}()
private var kUsageModels = [
UsagesModel(color: .red, value: 110.58),
UsagesModel(color: .green, value: 5.23),
UsagesModel(color: .blue, value: 1.25),
UsagesModel(color: .yellow, value: 0.58),
UsagesModel(color: .purple, value: 0.31),
UsagesModel(color: .lightGray, value: 32.51),
]
// Update the multiprogressBar
multipleProgressView.updateViews(usageModels: kUsageModels)