Updating notification badge number base on the list iOS swift - ios

Dear all iOS experts.... I'm currently implementing Notification badge on segment Controls
Below are my codes
func addCounter(count: Int)->UIView {
// Count > 0, show count
if count > 0 {
// Create label
let fontSize: CGFloat = 10
let label = UILabel()
label.font = UIFont.systemFont(ofSize: fontSize)
label.textAlignment = .center
label.textColor = .white
label.backgroundColor = .red
// Add count to label and size to fit
label.text = "\(NSNumber(value: count))"
label.sizeToFit()
// Adjust frame to be square for single digits or elliptical for numbers > 9
var frame: CGRect = label.frame
frame.size.height += CGFloat(Int(0.4 * fontSize))
frame.size.width = (count <= 9) ? frame.size.height : frame.size.width + CGFloat(Int(fontSize))
label.frame = frame
// Set radius and clip to bounds
label.layer.cornerRadius = frame.size.height / 2.0
label.clipsToBounds = true
// Show label in accessory view and remove disclosure
return label
} else {
return UIView()
}
and my code under ViewdidLoad()
self.badge1.addSubview(self.addCounter(count: 0))
as shown above count:0 is the number of notification display under the badge. May I know is there any way for me to make the count as dynamic value based on the number of notification I received from the list?

Make a subclass of UIView named BadgeView with a property badgecount. It’s a UIView with a label inside, with the label hidden when the count is zero.
Create it, store it away, add it to your badge in viewDidLiad, and change its badgecount when needed.

Related

How to change view size according to its content in iOS swift

I am working on photo editing app. I want to put some label and images in a view which is named as 'stickerView'. All I want when content size in that view change the stickerview also change its height and width accordingly.
For sticker view I am using https://github.com/injap2017/StickerView this library.
its working fine with images but with label its not adjust label font according to view.
I use this class to set the font according to the view
import UIKit
class FlexiFontLabel: UILabel {
// Boundary of minimum and maximum
private let maxFontSize = CGFloat(100)
private let minFontSize = CGFloat(15)
// Margin of error is needed in binary search
// so we can return when reach close enough
private let marginOFError = CGFloat(0.5)
// layoutSubviews() will get called while updating
// font size so we want to lock adjustments if
// processing is already in progress
private var isUpdatingFontSize = false
// Once this is set to true, the label should only
// only support multiple lines rather than one
var doesAdjustFontSizeToFitFrame = false
{
didSet
{
if doesAdjustFontSizeToFitFrame
{
numberOfLines = 0
}
}
}
// Adjusting the frame of the label automatically calls this
override func layoutSubviews()
{
super.layoutSubviews()
// Make sure the label is set to auto adjust the font
// and it is not currently processing the font size
if doesAdjustFontSizeToFitFrame
&& !isUpdatingFontSize
{
adjustFontSizeIfRequired()
}
}
/// Adjusts the font size to fit the label's frame using binary search
private func adjustFontSizeIfRequired()
{
guard let currentText = text,
var currentFont = font else
{
print("failed")
return
}
// Lock function from being called from layout subviews
isUpdatingFontSize = true
// Set max and min font sizes
var currentMaxFontSize = maxFontSize
var currentMinFontSize = minFontSize
while true
{
// Binary search between min and max
let midFontSize = (currentMaxFontSize + currentMinFontSize) / 2;
// Exit if approached minFontSize enough
if (midFontSize - currentMinFontSize <= marginOFError)
{
// Set min font size and exit because we reached
// the biggest font size that fits
currentFont = UIFont(name: currentFont.fontName,
size: currentMinFontSize)!
break;
}
else
{
// Set the current font size to the midpoint
currentFont = UIFont(name: currentFont.fontName,
size: midFontSize)!
}
// Configure an attributed string which can be used to find an
// appropriate rectangle for a font size using its boundingRect
// function
let attribute = [NSAttributedString.Key.font: currentFont]
let attributedString = NSAttributedString(string: currentText,
attributes: attribute)
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin,
.usesFontLeading]
// Get a bounding box with the width of the current label and
// an unlimited height
let constrainedSize = CGSize(width: frame.width,
height: CGFloat.greatestFiniteMagnitude)
// Get the appropriate rectangle for the text using the current
// midpoint font
let newRect = attributedString.boundingRect(with: constrainedSize,
options: options,
context: nil)
// Get the current area of the new rect and the current
// label's bounds
let newArea = newRect.width * newRect.height
let currentArea = bounds.width * bounds.height
// See if the new frame is lesser than the current label's area
if newArea < currentArea
{
// The best font size is in the bigger half
currentMinFontSize = midFontSize + 1
}
else
{
// The best font size is in the smaller half
currentMaxFontSize = midFontSize - 1
}
}
// set the font of the current label
font = currentFont
// Open label to be adjusted again
isUpdatingFontSize = false
}
}
here I set sticker view and label:
var testLabel = FlexiFontLabel(frame: CGRect.init(x: 0, y: 0, width: 300, height: 50))
testLabel.text = "Test Label"
testLabel.textAlignment = .left
testLabel.backgroundColor = .blue
testLabel.adjustsFontForContentSizeCategory = true
testLabel.numberOfLines = 1
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.2
testLabel.font = testLabel.font.withSize(testLabel.frame.height * 2/3)
testLabel.doesAdjustFontSizeToFitFrame = true
let stickerView2 = StickerView.init(contentView: testLabel)
stickerView2.center = CGPoint.init(x: 100, y: 100)
stickerView2.delegate = self
stickerView2.setImage(UIImage.init(named: "Close")!, forHandler: StickerViewHandler.close)
stickerView2.setImage(UIImage.init(named: "Rotate")!, forHandler: StickerViewHandler.rotate)
stickerView2.showEditingHandlers = false
self.view.addSubview(stickerView2)
its some how working fine for me but when I change font size of label using slider its size does not change.
So my question is how to change font size and stickerview size accordingly.
if I change font size using slider label size and stickerview size change accordingly and if I change view width and height its change label font size accordingly.
help will be appreciated thanks.

Auto-sizing a UILabel without setting an explicit height

How do I get a multi-line label to size itself? I don't want to set an explicit height for it but I do need to place it in view.
The way my app is built, we explicitly set frames and origins rather than using NSLayoutConstraints. It's a mature app so this isn't up for discussion.
I'd like to be able to give my UILabel an origin and a width and let it figure its own height out.
How can I do this? This is my playground code:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 180))
view.backgroundColor = .white
let l = UILabel()
l.text = "this is a really long label that should wrap around and stuff. it should maybe wrap 2 or three times i dunno"
l.textColor = .black
l.lineBreakMode = .byWordWrapping
l.numberOfLines = 0
l.textAlignment = .center
l.sizeToFit()
let margin: CGFloat = 60
view
view.addSubview(l)
l.frame = CGRect(x: margin, y: 0, width: view.bounds.width - (margin * 2), height: 100)
// I don't want to do this ^^
This may do what you want...
As requested, you want to set the .origin and .width of a UILabel and have it set its own .height based on the text.
class ZackLabel: UILabel {
override public func layoutSubviews() {
super.layoutSubviews()
let h = sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
self.frame.size.height = h.height
}
}
class ViewController: UIViewController {
var testLabel: ZackLabel!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// instantiate a 300 x 180 UIView at 20, 80
let myView = UIView(frame: CGRect(x: 20, y: 80, width: 300, height: 180))
myView.backgroundColor = .white
// instantiate a ZackLabel
testLabel = ZackLabel()
testLabel.text = "this is a really long label that should wrap around and stuff. it should maybe wrap 2 or three times i dunno"
testLabel.textColor = .black
testLabel.lineBreakMode = .byWordWrapping
testLabel.numberOfLines = 0
testLabel.textAlignment = .center
// set background color so we can see its frame
testLabel.backgroundColor = .cyan
let margin: CGFloat = 60
// set label's origin
testLabel.frame.origin = CGPoint(x: margin, y: 0)
// set label's width (label will set its own height)
testLabel.frame.size.width = myView.bounds.width - margin * 2
// add the view
view.addSubview(myView)
// add the label to the view
myView.addSubview(testLabel)
// add a tap recognizer so we can change the label's text at run-time
let rec = UITapGestureRecognizer(target: self, action: #selector(tapFunc(_:)))
view.addGestureRecognizer(rec)
}
#objc func tapFunc(_ sender: UITapGestureRecognizer) -> Void {
testLabel.text = "This is dynamic text being set."
}
}
Result (on an iPhone 8):
and, after tapping on the (yellow) view, dynamically changing the text:
label.sizeThatFits(CGSize(width: <your required width>, height: CGFloat.greatestFiniteMagnitude))
This returns the labels needed size, growing infinitely in height, but fitted to your required width. I've occasionally noticed minor inaccuracies with this function (rounding error?), so I tend to bump the width and height by 1 just to be safe.
UILabel comes with an intrinsic size that should be calculated based on the text and the label's .font property. You may need to add a margin to it...
var height = l.intrinsicContentSize.height
height += margin
l.frame = CGRect(x: margin, y: 0, width: view.bounds.width - (margin * 2), height: height)
Failing that, maybe you can try something like:
let size = CGSize(width: view.bounds.width - (margin * 2), height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
var estimatedFrame = CGRect()
if let font = l.font {
estimatedFrame = NSString(string: l.text).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: font], context: nil)
}
//if you need a margin:
estimatedFrame.height += margin
l.frame = estimatedFrame
Give your UILabel as a UIScrollview or UITableView cell subview.
Then you setup UILabel leading, tralling, top, bottom constrain.
If you give UITableview then set table view hight auto dynamic. If you give UIScrollview
just set UILabel bottom constrain priority low

Label not display in subview of UITableView

I am designing an iOS hamburger menu with a table view in swift3
I want to display the number of notifications in menu tab.
let pieNotifi = UIImageView(frame: CGRectMake(cell.contentView.frame.size.width-40, cell.contentView.frame.size.height/2-7, 30, 20))
let pieLabNotifi = UILabel(frame: CGRectMake(cell.contentView.frame.size.width-40, cell.contentView.frame.size.height/2-7, 30, 20))
let subviews = self.view.viewWithTag(100001)
if myGlobal.unreaddot != 0 { //just a count for notifcaiton
if subviews == nil {
pieNotifi.tag = 100001;
cell.addSubview(pieNotifi)
let pieShape = pieNotifi.layer;
pieShape.backgroundColor = UIColor.red.cgColor
pieShape.cornerRadius = pieShape.bounds.width / 4
pieLabNotifi.textColor = UIColor.white
pieLabNotifi.adjustsFontSizeToFitWidth = true;
pieLabNotifi.minimumScaleFactor = 0.5
pieLabNotifi.textAlignment = NSTextAlignment.center
pieNotifi.addSubview(pieLabNotifi)
pieLabNotifi.text = String(myGlobal.unreaddot)
}
else{
pieLabNotifi.text = String(myGlobal.unreaddot)
}
}else{
let subviews = cell.viewWithTag(100001)
subviews?.removeFromSuperview()
print("remove red dot")
}
however, it only display the red colour layer but do not have the label. refer to the picture shown below
enter image description here
I had tried to set
.sendSubview(to back:)
.bringSubview(toFront: )
.layer.zPosition = 1
all these key word not work for me.
Can anyone advise me to solve this problem?
You are not adding subview to UITableView. According to your code you are adding a subview to the cell of UITableView.
Try to add the subview to the content view of the cell, like:
cell.contentView.addSubView(pieNotifi)
i think the issue is with the positions of your label. You are giving your label x coordinate as cell.contentView.frame.size.width-40, but you want to add it inside the red image view. Changing the x position and y position to 0 (or whatever value you need )for your label will solve your problem. I have modified your code accordingly :
let pieNotifi = UIImageView(frame: CGRectMake(cell.contentView.frame.size.width-40, cell.contentView.frame.size.height/2-7, 30, 20))
let pieLabNotifi = UILabel(frame: CGRectMake(0,0, 30, 20))
let subviews = self.view.viewWithTag(100001)
if myGlobal.unreaddot != 0 { //just a count for notifcaiton
if subviews == nil {
pieNotifi.tag = 100001;
cell.addSubview(pieNotifi)
let pieShape = pieNotifi.layer;
pieShape.backgroundColor = UIColor.red.cgColor
pieShape.cornerRadius = pieShape.bounds.width / 4
pieLabNotifi.textColor = UIColor.white
pieLabNotifi.adjustsFontSizeToFitWidth = true;
pieLabNotifi.minimumScaleFactor = 0.5
pieLabNotifi.textAlignment = NSTextAlignment.center
pieNotifi.addSubview(pieLabNotifi)
pieLabNotifi.text = String(myGlobal.unreaddot)
}
else{
pieLabNotifi.text = String(myGlobal.unreaddot)
}
}else{
let subviews = cell.viewWithTag(100001)
subviews?.removeFromSuperview()
print("remove red dot")
}
Also try to see your label position in UiDebugger , you might discover that label is added at wrong position.

Swift 3 - Adjust Font Size to Fit Width, Multiple Lines

I have a UILabel and it is set to 42.0 pt font, and the width of the label is set using autoconstraints based on factors other than the label itself (aka the things to the right and left of the label determine the label's width).
I would like to auto-adjust the font size to fit the width of the label, however also break to two lines when it can. Similar to this:
I know you can adjust the font size to fit the width of the label, but only when the number of lines is set to 1.
How would I accomplish this?
This will work..
Set minimum scale factor for your label. as shown in this image.
Set adjustsFontSizeToFitWidth to true
Set number of lines = 2 // or zero (0) if you want more number of lines
Set line breaking mode to '.byTruncatingTail' for 2 lines
Swift 5
Set number of lines zero for dynamic text information, it will be useful for varying text.
var label = UILabel()
let stringValue = "A label\nwith\nmultiline text."
label.text = stringValue
label.numberOfLines = 2 // 0
label.lineBreakMode = .byTruncatingTail // or .byWrappingWord
label.minimumScaleFactor = 0.5 // It is not required but nice to have a minimum scale factor to fit text into label frame
label.adjustsFontSizeToFitWidth = true //needed in Swift 5
Also, don't set height constraint for your label more than 2 lines.
Interesting question. Here's my solution:
let labelText = self.mylabel.text //where mylabel is the label
let labelSeperated = self.labelText.components(seperatedBy: " ")
if labelSeperated.count > 1 {
myLabel.lineBreakMode = .byWordWrapping
myLabel.numberOfLines = 0
} else {
myLabel.numberOfLines = 1
myLabel.adjustsFontSizeToFitWidth = true
}
Put this code where the label will be changed. It sets the line number to 0 if there are two or more numbers, otherwise set to 1 line only.
If you want to resize multi-line labels, check out this blog post.
Swift 5
func setFontForLabel(label:UILabel, maxFontSize:CGFloat, minFontSize:CGFloat, maxLines:Int) {
var numLines: Int = 1
var textSize: CGSize = CGSize.zero
var frameSize: CGSize = CGSize.zero
let font: UIFont = label.font.withSize(maxFontSize)
frameSize = label.frame.size
textSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: font])
// Determine number of lines
while ((textSize.width/CGFloat(numLines)) / (textSize.height * CGFloat(numLines)) > frameSize.width / frameSize.height) && numLines < maxLines {
numLines += 1
}
label.font = font
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = numLines
label.minimumScaleFactor = minFontSize/maxFontSize
}
Swift 3
I looked at the post that paper111 posted. Unfortunately it's in Obj-C and the sizeWithFont: ,constrainedToSize: , lineBreakMode: method has been deprecated. (- - );
His answer was good, but still didn't provide a fixed size. What I did was to start with a UILabel that had everything but the height (this is probably the same for most people).
let myFrame = CGRect(x: 0, y:0, width: 200, height: self.view.height)
let myLbl = UILabel(frame: myFrame)
let finalHeight:CGFloat = 300
myLbl.font = UIFont(name: "Chalkduster", size: 16.0)
myLbl.lineBreakMode = .byWordWrapping
myLbl.numberOfLines = 0
myLbl.text = "Imagine your long line of text here"
addSubview(myLbl)
myLbl.sizeToFit()
guard myLbl.frame.height > finalHeight else { return }
var fSize:CGFloat = 16 //start with the default font size
repeat {
fSize -= 2
myLbl.font = UIFont(name: "Chalkduster", size: fSize)
myLbl.sizeToFit()
} while myLbl.frame.height > finalHeight
You can see that there's a guard blocking the resize if it's not needed. Also, calling sizeToFit() many times isn't ideal, but I can't think of another way around it. I tried to use myLbl.font.withSize(fSize) in the loop but it wouldn't work, so I used the full method instead.
Hope it works for you!
Swift 5
extension UILabel{
func adjustsFontSizeToFit(maxFontSize:CGFloat,width:CGFloat,height:CGFloat) {
self.numberOfLines = 0
var fontSize:CGFloat = maxFontSize
if self.sizeThatFits(CGSize(width: width, height: .infinity)).height > height{
while self.sizeThatFits(CGSize(width: width, height: .infinity)).height > height{
fontSize -= 1
self.font = self.font.withSize(fontSize)
}
}
}
}
#Krunal's answer helped me but it doesn't work when you have unknown number of lines so here's the solution I came up with. You can also set the maximum and minimum font size. Hope this helps someone!
Swift 2.2 - Sorry, haven't migrated to Swift 3 yet.
func setFontForLabel(label:UILabel, maxFontSize:CGFloat, minFontSize:CGFloat, maxLines:Int) {
var numLines: Int = 1
var textSize: CGSize = CGSizeZero
var frameSize: CGSize = CGSizeZero
var font: UIFont = UIFont.systemFontOfSize(maxFontSize)
frameSize = label.frame.size
textSize = (label.text! as NSString).sizeWithAttributes([NSFontAttributeName: font])
// Determine number of lines
while ((textSize.width/CGFloat(numLines)) / (textSize.height * CGFloat(numLines)) > frameSize.width / frameSize.height) && numLines < maxLines {
numLines += 1
}
label.font = font
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = numLines
label.minimumScaleFactor = minFontSize/maxFontSize
}

How do I set adaptive multiline UILabel text?

I have a UILabel named titleLabel in my storyboard nib set to its default height. I want it to programatically expand in height to fit it's content. Here is what I have tried so far:
// just setting content
titleLabel.text = "You don't always know what you are getting with mass-market cloud computing services. But with SimpliCompute, the picture is clear. SimpliCompute gives you powerful virtual servers you can deploy using just your web browser. That’s enterprise grade technology you can deploy and control on-the-fly."
titleLabel.numberOfLines = 0
titleLabel.preferredMaxLayoutWidth = 700
titleLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
titleLabel.sizeToFit()
None of this works for me in any combination! I always only see one line of text in my UILabel. What am I doing wrong?
I absolutely need the text content to be variable.
I kind of got things working by adding auto layout constraints:
But I am not happy with this. Took a lot of trial and error and couldn't understand why this worked.
Also I had to add to use titleLabel.numberOfLines = 0 in my ViewController
I know it's a bit old but since I recently looked into it :
let l = UILabel()
l.numberOfLines = 0
l.lineBreakMode = .ByWordWrapping
l.text = "BLAH BLAH BLAH BLAH BLAH"
l.frame.size.width = 300
l.sizeToFit()
First set the numberOfLines property to 0 so that the device understands you don't care how many lines it needs.
Then specify your favorite BreakMode
Then the width needs to be set before sizeToFit() method. Then the label knows it must fit in the specified width
This is much better approach if you are looking for multiline dynamic text label which exactly takes the space based on its text.
No sizeToFit, preferredMaxLayoutWidth used
Below is how it will work.
Lets set up the project. Take a Single View application and in Storyboard Add a UILabel and a UIButton. Define constraints to UILabel as below snapshot:
Set the Label properties as below image:
Add the constraints to the UIButton. Make sure that vertical spacing of 100 is between UILabel and UIButton
Now set the priority of the trailing constraint of UILabel as 749
Now set the Horizontal Content Hugging and Horizontal Content Compression properties of UILabel as 750 and 748
Below is my controller class. You have to connect UILabel property and Button action from storyboard to viewcontroller class.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
var count = 0
let items = ["jackson is not any more in this world", "Jonny jonny yes papa eating sugar no papa", "Ab", "What you do is what will happen to you despite of all measures taken to reverse the phenonmenon of the nature"]
#IBAction func updateLabelText(sender: UIButton) {
if count > 3 {
count = 0
}
textLabel.text = items[count]
count = count + 1
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//self.textLabel.sizeToFit()
//self.textLabel.preferredMaxLayoutWidth = 500
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Thats it. This will automatically resize the UILabel based on its content and also you can see the UIButton is also adjusted accordingly.
It should work. Try this
var label:UILabel = UILabel(frame: CGRectMake(10
,100, 300, 40));
label.textAlignment = NSTextAlignment.Center;
label.numberOfLines = 0;
label.font = UIFont.systemFontOfSize(16.0);
label.text = "First label\nsecond line";
self.view.addSubview(label);
With Graphical User Interface (GUI) in Xcode, you can do the following:
Go to "Attribute Inspector" and set Lines value to 0. By default, it is set to 1.
The Label text can be written in multi-line by hitting option + return.
Now, go to "Size Inspector" and set the width, height, X & Y position of the Label.
That's all.
Programmatically, Swift
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.titleView.numberOfLines = 2
Programmatically in Swift 5 with Xcode 10.2
Building on top of #La masse's solution, but using autolayout to support rotation
Set anchors for the view's position (left, top, centerY, centerX, etc). You can also set the width anchor or set the frame.width dynamically with the UIScreen extension provided (to support rotation)
label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
self.view.addSubview(label)
// SET AUTOLAYOUT ANCHORS
label.translatesAutoresizingMaskIntoConstraints = false
label.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 20).isActive = true
label.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -20).isActive = true
label.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20).isActive = true
// OPTIONALLY, YOU CAN USE THIS INSTEAD OF THE WIDTH ANCHOR (OR LEFT/RIGHT)
// label.frame.size = CGSize(width: UIScreen.absoluteWidth() - 40.0, height: 0)
label.text = "YOUR LONG TEXT GOES HERE"
label.sizeToFit()
If setting frame.width dynamically using UIScreen:
extension UIScreen { // OPTIONAL IF USING A DYNAMIC FRAME WIDTH
class func absoluteWidth() -> CGFloat {
var width: CGFloat
if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
width = self.main.bounds.height // Landscape
} else {
width = self.main.bounds.width // Portrait
}
return width
}
}
extension UILabel {
var textSize: CGSize { text?.size(withAttributes: [.font: font!]) ?? .zero }
func setSizeForText(_ str: String, maxWidth: CGFloat) {
text = str
let dividedByMaxWidth = Int(textSize.width / maxWidth)
if dividedByMaxWidth == 0 {
frame.size = textSize
} else {
numberOfLines = dividedByMaxWidth + 1
frame.size = CGSize(width: maxWidth, height: frame.size.height * CGFloat(numberOfLines))
sizeToFit()
}
}
}
sizeToFit() in the end will shrink the label's width to the widest line after word break
This has worked for me:
Set the numberOfLines property of UILabel to 0
add this line: yourLabel.sizeToFit() after assigning text to the UILabel

Resources