Generate random questions in swift - swift

Can anyone help? I have a true or false game, however, I want the questions to be shuffled/random. I know there is previous shuffle answers but I can't get my head around it. For example, the questions in the append section should appear at random. I have been trying different things for days. I had a look at arc4random but I had a hard time implementing it. I don't want to be spoon fed but a decent answer is all I am looking for.
Here is my swift code
// ViewController.swift
// TrueOrFalse
//
//
//
import UIKit
class ViewController: UIViewController {
// Classes
class newLabel:UILabel {
convenience required init(width:CGFloat, height:CGFloat, framewidth: CGFloat, frameheight: CGFloat) {
self.init(frame: CGRectMake(0, 0, framewidth, frameheight))
self.center = CGPointMake(width, height)
self.font = UIFont(name: "HelveticaNeue-Medium", size: 18)
}
}
class newButton:UIButton {
var button:UIButton!
convenience required init(width:CGFloat, height:CGFloat, framewidth: CGFloat, frameheight: CGFloat) {
self.init(frame: CGRectMake(0, 0, framewidth, frameheight))
self.button = UIButton.buttonWithType(UIButtonType.System) as UIButton
self.center = CGPointMake(width, height)
}
}
let width:CGFloat = UIScreen.mainScreen().bounds.width
let height:CGFloat = UIScreen.mainScreen().bounds.height
var model:ToFModel = ToFModel()
var statement:UILabel!
var correctCount:newLabel!
var incorrectCount:newLabel!
var timerCount:newLabel!
var correctAmt:Int = 0
var incorrectAmt:Int = 0
var buttonTrue:newButton!
var buttonFalse:newButton!
var count:Int = 0
var counter:Int = 60
var answer:String!
var wall:UIView!
var lastBool:String!
var displayLastBool:newLabel!
var questionNumber:newLabel!
var frame:UIImageView!
var image:UIImage!
var splashImage:UIImage!
var splashLandingText:UILabel!
var splashButtonStart:newButton!
var timer = NSTimer()
var userDefaults = NSUserDefaults.standardUserDefaults()
func nextIter() {
statement.text = model.statements[count][0]
answer = model.statements[count][1]
questionNumber.text = "Question- \(count+1)/\(model.statements.count)"
if count == model.statements.count-1{
statement.textColor = UIColor.redColor()
}
else{
statement.textColor = UIColor.blackColor()
}
if count > 0 && count < model.statements.count {
// displayLastBool.text = ""
view.addSubview(displayLastBool)
}
else {
displayLastBool.text = ""
}
count += 1
}
// View functions
func start(sender:newButton) {
count = 0
frame.image = image
displayLastBool = newLabel(width: 170, height: height*0.27, framewidth: 200, frameheight: 50)
//Timer
timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("updateCounter"), userInfo: nil, repeats: true)
// Put the image into the frame
// Remove current stuff
splashButtonStart.removeFromSuperview()
splashLandingText.removeFromSuperview()
// Add stuff to view
view.addSubview(timerCount)
view.addSubview(correctCount)
view.addSubview(incorrectCount)
view.addSubview(statement)
view.addSubview(buttonTrue)
view.addSubview(buttonFalse)
view.addSubview(questionNumber)
nextIter()
}
func splashPage() {
count = 0
displayLastBool?.removeFromSuperview()
// Put up the wall
view.addSubview(wall)
// Hang the frame on the wall
wall.addSubview(frame)
// Put up the text
view.addSubview(splashLandingText)
// Put up the button
view.addSubview(splashButtonStart)
frame.image = splashImage
}
func reset() {
correctCount.hidden = true
incorrectCount.hidden = true
count = 0
incorrectAmt = 0
correctAmt = 0
correctCount.removeFromSuperview()
incorrectCount.removeFromSuperview()
statement.removeFromSuperview()
buttonFalse.removeFromSuperview()
buttonTrue.removeFromSuperview()
questionNumber.removeFromSuperview()
displayLastBool.removeFromSuperview()
splashPage()
}
// Boolean detection
func boolResponse(sender:newButton) {
if count == model.statements.count {
if answer == String(sender.tag) {
count = 0
correctAmt = 0
incorrectAmt = 0
correctCount.hidden = true
incorrectCount.hidden = true
displayLastBool.hidden = false
timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("updateCounter"), userInfo: nil, repeats: true)
}
else {
timer.invalidate()
reset()
}
}
else if answer == String(sender.tag) {
lastBool = "Correct"
displayLastBool.textColor = UIColor.greenColor()
// Correct count increments here
correctAmt += 1
}
else if answer != String(sender.tag) {
lastBool = "Incorrect"
displayLastBool.textColor = UIColor.redColor()
// Incorrect count increments here
incorrectAmt += 1
count = 9;
}
if count == model.statements.count-1 {
correctCount.hidden = false
incorrectCount.hidden = false
correctCount.text = "Correct- \(correctAmt)"
//incorrectCount.text = "Incorrect- \(incorrectAmt)"
//Saving Highscore
var highscore=userDefaults.integerForKey("highscore")
if(correctAmt>highscore)
{
userDefaults.setInteger(correctAmt, forKey: "highscore")
}
var highscoreshow=userDefaults.integerForKey("highscore")
incorrectCount.text = "High Score- \(highscoreshow)"
timer.invalidate()
counter = 60
timerCount.text = String(counter)
timerCount.textColor = UIColor.blackColor()
}
nextIter()
}
//Timer update function
func updateCounter() {
timerCount.text = String(counter--)
if counter <= 9{
timerCount.textColor = UIColor.redColor()
}else{
timerCount.textColor = UIColor.blackColor()
}
if counter == 0{
count=9
nextIter()
resetTimer()
}
}
//Timer reset function
func resetTimer() {
correctCount.hidden = false
incorrectCount.hidden = false
correctCount.text = "Correct- \(correctAmt)"
incorrectCount.text = "Incorrect- \(incorrectAmt)"
timer.invalidate()
counter = 60
timerCount.text = String(counter)
timerCount.textColor = UIColor.blackColor()
// displayLastBool.hidden = true
//questionNumber.hidden = true
}
// Make an image view that you can modify from any function in this class
var imageView:UIImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Append
model.statementsAppend("Sneezes regularly exceed 200 mph.", bool: "0")
model.statementsAppend("Virtually all Las Vegas gambling casinos ensure that they have no clocks.", bool: "1")
model.statementsAppend("Two human lungs have a surface area of approximately 750 square feet.", bool: "1")
model.statementsAppend("The 'black box' in an airplane is colored black.", bool: "0")
model.statementsAppend("The Statue of Liberty was a gift from Germany to America.", bool: "0")
model.statementsAppend("Ozone is helpful in the trophosphere but damaging to the stratosphere.", bool: "0")
model.statementsAppend("The can opener was invented after the can.", bool: "1")
model.statementsAppend("The world's oldest tree is OVER 9000 years old.", bool:"1")
model.statementsAppend("Emus can fly.", bool: "0")
model.statementsAppend("Game Over \n Play again?", bool: "1")
// Make the wall, the frame, and the image for the frame
wall = UIView(frame: CGRectMake(0, 0, width, height))
frame = UIImageView(frame: CGRectMake(0, 0, width, height))
//Load the text
statement = UILabel(frame: CGRectMake(0, 0, 280, height))
statement.center = CGPointMake(width/2, height*0.5)
statement.textAlignment = NSTextAlignment.Center
statement.lineBreakMode = NSLineBreakMode.ByWordWrapping
statement.numberOfLines = 99
statement.font = UIFont(name: "Arial", size: 24)
splashImage = UIImage(named: "splashGradient")
image = UIImage(named: "minimalGradient")
//Load the counts
timerCount = newLabel(width: 100, height: height*0.10, framewidth:200, frameheight:100)
correctCount = newLabel(width: 159, height: height*0.15, framewidth:100, frameheight:50)
incorrectCount = newLabel(width:147, height: height*0.19, framewidth:130, frameheight:50)
questionNumber = newLabel(width: 197, height: height*0.23, framewidth: 200, frameheight: 50)
timerCount.center = CGPointMake(width/2, height*0.09)
timerCount.textAlignment = NSTextAlignment.Center
timerCount.font = UIFont(name: "AvenirNext-DemiBold", size: 40)
timerCount.text = String(counter)
correctCount.text = "Correct- \(correctAmt)"
incorrectCount.text = "High Score- \(incorrectAmt)"
incorrectCount.textColor = UIColor.greenColor()
lastBool = "Incorrect"
correctCount.hidden = true
incorrectCount.hidden = true
questionNumber.hidden = true
//Load the buttons
buttonTrue = newButton(width: 80, height: height/1.2, framewidth: 111, frameheight: 45)
buttonFalse = newButton(width: 240, height: height/1.2, framewidth: 111, frameheight: 45)
// Style of button
buttonTrue.setBackgroundImage(UIImage(named: "true"), forState: UIControlState.Normal)
buttonFalse.setBackgroundImage(UIImage(named: "false"), forState: UIControlState.Normal)
// .tag
buttonTrue.tag = 1
buttonFalse.tag = 0
// Functions if button is clicked
buttonTrue.addTarget(self, action: "boolResponse:", forControlEvents: UIControlEvents.TouchUpInside)
buttonFalse.addTarget(self, action: "boolResponse:", forControlEvents: UIControlEvents.TouchUpInside)
// Make the splash button
splashButtonStart = newButton(width: width/2, height: height*0.8, framewidth: 244, frameheight: 58)
splashButtonStart.setBackgroundImage(UIImage(named: "splashButton"), forState: UIControlState.Normal)
splashButtonStart.addTarget(self, action: "start:", forControlEvents: UIControlEvents.TouchUpInside)
// Configure the text on the splash page
splashLandingText = UILabel(frame: CGRectMake(0, 0, 280, 280))
splashLandingText.center = CGPointMake(width/2, height*0.4)
splashLandingText.textAlignment = NSTextAlignment.Center
splashLandingText.numberOfLines = 99
splashLandingText.lineBreakMode = NSLineBreakMode.ByWordWrapping
splashLandingText.font = UIFont(name: "AvenirNext-DemiBold", size: 24)
splashLandingText.text = "Impossible True or False \n \n Are you ready to start your journey?"
// Configure the statement
splashPage()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

I didn't try to wade through your code. It's too much. (tldr)
Here is a general solution. You'll need to adapt it to your needs.
Create a struct that contains a question, the possible answers, and the correct answer (if they are all true/false questions you could skip the possible answers part.) Let's call it questionStruct.
Then create an array of questionStruct structures. Populate it with questions.
Copy the list of all questions into a working array of questions. Let's call it remainingQuestions
Then user arc4random_uniform to select a question at random and remove it from the remaining questions array
let index = arc4random_uniform(remainingQuestions.count)
let aQuestion = remainingQuestions.removeAtIndex(index)
When the remainingQuestions array is empty, repopulate it with the array of all questions to start over.

Related

How to use search in icarousel current index item?

Hey all I am just wondering how can I use a search bar to reload data in carousel item without reloading all other items?
below is the code for the carousel:
to Note the concerned one is if == "Languages"
lazy var car:iCarousel = {
let view = iCarousel()
view.frame = CGRect(x:0, y:Int(CGFloat(Double(Int(Float(bounds.size.height*0.05))) + globW )) , width: Int(Float(bounds.size.width)*0.85), height: Int(Float(bounds.size.height)*0.75))
// view.type = .coverFlow
view.type = .wheel
view.isVertical = true
view.bounces = true
view.decelerationRate = 0.0
view.clipsToBounds=true
// view.isScrollEnabled = false
// view.backgroundColor = .red
return view
}()
below is the concerned case for the carousel item:
case "C":
let titleV:UILabel!
let titleOption:NSMutableAttributedString!
let fSAttr = [ NSAttributedString.Key.font: UIFont(name: "Helvetica", size: bounds.size.width*0.04)!,
NSAttributedString.Key.foregroundColor : UIColor.white,
]
titleOption = NSMutableAttributedString(string:currentField[index].placeholder!, attributes: fSAttr)
let widthFS = titleOption.width(containerHeight: v.bounds.width*0.5)
img.frame=CGRect(x: Double(v.bounds.width)*0.5-Double(bounds.size.width)*0.05, y: Double(v.bounds.height)*0.2, width:Double(bounds.size.width)*0.1, height: Double(bounds.size.width)*0.1)
img.image=UIImage(named: currentField[index].path!)?.resized(to: CGSize(width: CGFloat(bounds.size.width*0.1), height: CGFloat(bounds.size.width*0.1)))
v.addSubview(img)
titleV = UILabel(frame: CGRect(x:Int((v.bounds.width - widthFS)*0.5) , y: Int(-v.bounds.height*0.02), width: Int(Float(0.449*bounds.size.height)), height: Int(Float(0.567*bounds.size.height))))
titleV.backgroundColor = .clear
titleV.attributedText = titleOption
// v.bounds.height*0.33
// titleV.isEditable = false
v.addSubview(titleV)
currentCarView = v
if currentField[index].id == "Languages"{
let cont=UITextField()
// TRY TO RELOAD V AND VIEW
// find children and an id
// https://www.youtube.com/watch?v=mMr8fg5LZcU
// https://www.raywenderlich.com/4363809-uisearchcontroller-tutorial-getting-started
cont.tag = index
// cont.text = topkol
// cont.becomeFirstResponder()
cont.textColor = .white
cont.attributedPlaceholder = NSAttributedString(string: "Search", attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray])
cont.setBottomBorder(color:UIColor.gray.cgColor)
cont.frame = CGRect(x: CGFloat(Int(Float(car.bounds.width*0.4))), y: v.bounds.height*0.5 /*CGFloat(Int(Float(car.frame.height*0.5)))-25*/, width:CGFloat(Int(Float(car.bounds.width*0.6))), height: 50)
cont.backgroundColor = .clear
cont.addTarget(self, action: #selector(editSearch(_:)), for: UIControl.Event.editingChanged)
cont.addTarget(self, action: #selector(editSearch(_:)), for: .editingDidEndOnExit)
v.addSubview(cont)
}
for n in 0..<currentField[index].selections!.count{
let buttonVal = UIButton()
buttonVal.tag = index
buttonVal.accessibilityIdentifier = currentField[index].selections![n]
if currentField[index].id == "Languages"{
//replace with search array currentField[index].selections![n]
let languageComponent: [String] = currentField[index].selections![n].components(separatedBy: "-");
let lastElement = languageComponent[languageComponent.count - 1];
// let elem = languageComponent
//
let b = (n)/4
let x = 0.2 + Double(Int(Double((n))-floor(Double(b))*4))*0.2
let y = 0.1*floor(Double(b)) //0.05 0.55 0.1
// Double(v.bounds.height)*y
buttonVal.frame=CGRect(x: Double(v.bounds.width)*Double(round(x*10)/10)-12.5, y: Double(v.bounds.height)*y, width:Double(v.bounds.width)*0.11, height: Double(v.bounds.width)*0.11)
buttonVal.setTitle(countryFlag(country: lastElement), for: .normal)
Scroll.contentSize.height = CGFloat(Double(v.bounds.height)*y)+CGFloat(Double(v.bounds.height)*0.1)
Container.frame.size = CGSize(width: car.bounds.width, height: Scroll.contentSize.height)
Container.addSubview(buttonVal)
Scroll.addSubview(Container)
if !Scroll.isDescendant(of: v){
v.addSubview(Scroll)
}
// v.backgroundColor = .green
// print(countryName(countryCode: "DE")!)
} else {
let x = n == 0 ? 0.2 : n == 1 ? 0.5 : 0.8
// print("the W",v.bounds.width)
buttonVal.frame=CGRect(x: Double(v.bounds.width)*Double(x)-12.5, y: Double(v.bounds.height)*0.6, width:Double(v.bounds.width)*0.11, height: Double(v.bounds.width)*0.11)
let imageVal = UIImage(named: currentField[index].selections![n])?.resized(to: CGSize(width: CGFloat(bounds.size.width*0.1), height: CGFloat(bounds.size.width*0.1)))
buttonVal.setImage(imageVal!.resized(to: CGSize(width: v.bounds.width*0.075, height: v.bounds.width*0.075)), for: .normal)
v.addSubview(buttonVal)
}
buttonVal.layer.cornerRadius = v.bounds.width*0.055
if currentField[index].id == "Gender" {
if currentField[index].text == currentField[index].selections![n]{
//HERE
buttonVal.backgroundColor = .white
}
selArr.append(selButtons(name:buttonVal, tag:index))
} else {
for r in 0..<currentField[index].selected!.count{
if currentField[index].selections![n] == currentField[index].selected![r] {
buttonVal.backgroundColor = .white
}
}
}
buttonVal.addTarget(self, action: #selector(editTypeC(_:)), for: UIControl.Event.touchDown)
}
Here is the editfunction:
#objc func editSearch(_ sender:UITextField){
DispatchQueue.main.async { [self] in
sender.text = sender.text!
currentCarView.backgroundColor = .red
car.reloadData()
car.currentItemView?.reloadInputViews()
}
}
thank you all for your item, please. do suggest for better ways

i want to set the custom stacking button class's button text differently for each ViewController

I set the custom stackButtonView Like this.
I want to use this class to the another class with changing buttonText context.
["1","2","3","4"] >> ["a","b","c","d"]
I thought buttonText set to global property . Am I right? ..
class kindsButtonView: UIView{
//...
let buttonText: [String] = [
"1","2","3","4"
]
public func addButtonsToStackView() {
let numberOfButtons = buttonText.count
let column = 2
let row: Int
if numberOfButtons % column != 0 {
row = (numberOfButtons / column) + 1
} else {
row = numberOfButtons / column
}
for i in 0 ..< row {
let horizontalSv = UIStackView()
horizontalSv.axis = .horizontal
//horizontalSv.alignment = .fill
horizontalSv.distribution = .fillEqually
horizontalSv.spacing = 8
for j in 0 ..< column {
if buttonText.count == i*column + j {
let hideButton = SurveyButton()
// make tranparant !
hideButton.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0).cgColor
hideButton.layer.shadowOffset = CGSize(width: 0, height: 0)
hideButton.layer.shadowOpacity = 0
hideButton.layer.shadowRadius = 0
hideButton.layer.masksToBounds = false
hideButton.layer.shadowOffset = CGSize(width: 0, height: 0)
horizontalSv.addArrangedSubview(hideButton)
break
}
let button = SurveyButton()
button.setTitle("\(buttonText[ i*column + j ])",for: .normal)
button.titleLabel?.font = UIFont(name: "AvenirNext-DemiBoldItalic", size: 15)
button.tag = i*column + j + 1
button.addTarget(self, action: #selector(handleCleaningKinds(_:)), for: .touchUpInside)
horizontalSv.addArrangedSubview(button)
}
stackView1.addArrangedSubview(horizontalSv)
}
stackView.addArrangedSubview(stackView1)
}
Yes, your approach will mostly work.
If you want me to suggest a few changes, then:
Place addButtonsToStackView() inside an appropriate extension class (in your case UIView).
Use buttonText as a private property inside the class where you will use it.
Pass the buttonText properly into addButtonsToStackView() preferably as a parameter.

Line chart fill color is faded

I am trying to setup a line chart with one fill colour but for some reason, the fill colour is faded.
Example
Both the random view I have added to middle of screen and the fill colour of the line chart are set to be red, but for some reason the fill colour of the chart is faded.
Can see code here
#IBOutlet var liveChart : LineChartView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Chart Tests"
configureChart(chart: liveChart)
var xAxis = [String]()
var yAxis = [Double]()
for _ in 0..<10
{
xAxis.append("")
let yVal = Double(randomBetweenNumbers(firstNum: 1.0, secondNum: 100.0))
yAxis.append(yVal)
}
setData(xAxisArray: xAxis, yAxisArray: yAxis, chart: liveChart)
let testView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
testView.center = view.center
testView.backgroundColor = UIColor.red
view.addSubview(testView)
}
func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}
func configureChart(chart : LineChartView)
{
chart.chartDescription?.text = ""
chart.noDataText = "Loading Data"
chart.backgroundColor = UIColor.clear
chart.drawGridBackgroundEnabled = false
chart.dragEnabled = true
chart.rightAxis.enabled = false
chart.leftAxis.enabled = true
chart.doubleTapToZoomEnabled = false
chart.legend.enabled = false
chart.pinchZoomEnabled = true
chart.highlightPerTapEnabled = false
chart.highlightPerDragEnabled = false
chart.xAxis.enabled = false
chart.leftAxis.drawAxisLineEnabled = false
chart.leftAxis.drawGridLinesEnabled = false
chart.leftAxis.labelCount = 5
chart.leftAxis.forceLabelsEnabled = true
}
func setData(xAxisArray : [String], yAxisArray : [Double], chart : LineChartView)
{
var yVals1 : [ChartDataEntry] = [ChartDataEntry]()
if(xAxisArray.count > 0)
{
for i in 0 ..< xAxisArray.count
{
let chartEntry = ChartDataEntry(x: Double(i), y: yAxisArray[i], data: nil)
yVals1.append(chartEntry)
}
}
let set1: LineChartDataSet = LineChartDataSet(values: yVals1, label: "")
set1.fillColor = UIColor.red
set1.drawFilledEnabled = true
set1.drawCirclesEnabled = false
let data = LineChartData()
data.addDataSet(set1)
liveChart.data = data
}
Is there a way to fix this? Or is this just the way the fill colour of the chart works?
Edit:
I am using
https://github.com/danielgindi/Charts
I assume you use this library: https://github.com/kevinbrewster/SwiftCharts
So the LineChartView automatically set alpha for the fill color: https://github.com/kevinbrewster/SwiftCharts/blob/master/SwiftCharts/LineChart.swift#L758
try to set fillAlpha property of the data set

Swift add badge to navigation barButtonItem and UIButton

I am trying to display badge on my notification button, in app as displayed on AppIcon.
So far whatever i have researched is related to Obj. C, but nothing that specifically discussed way to implement that solution into Swift,
Please help to find a solution to add a custom class / code to achieve Badge on UiBarbutton and UiButton.
Researched so far:
https://github.com/Marxon13/M13BadgeView
along with MKBadge class etc.
There is a more elegant solution with an extension for UIButtonItem
extension CAShapeLayer {
func drawCircleAtLocation(location: CGPoint, withRadius radius: CGFloat, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
let origin = CGPoint(x: location.x - radius, y: location.y - radius)
path = UIBezierPath(ovalIn: CGRect(origin: origin, size: CGSize(width: radius * 2, height: radius * 2))).cgPath
}
}
private var handle: UInt8 = 0
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func addBadge(number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true) {
guard let view = self.value(forKey: "view") as? UIView else { return }
badgeLayer?.removeFromSuperlayer()
// Initialize Badge
let badge = CAShapeLayer()
let radius = CGFloat(7)
let location = CGPoint(x: view.frame.width - (radius + offset.x), y: (radius + offset.y))
badge.drawCircleAtLocation(location: location, withRadius: radius, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// Initialiaze Badge's label
let label = CATextLayer()
label.string = "\(number)"
label.alignmentMode = CATextLayerAlignmentMode.center
label.fontSize = 11
label.frame = CGRect(origin: CGPoint(x: location.x - 4, y: offset.y), size: CGSize(width: 8, height: 16))
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// Save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
func updateBadge(number: Int) {
if let text = badgeLayer?.sublayers?.filter({ $0 is CATextLayer }).first as? CATextLayer {
text.string = "\(number)"
}
}
func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
This great code was created by Stefano Vettor and you can find all the details at:
https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4
Working Solution :
Step 1:
Firstly create new swift file which is a subclass to UIButton as follows:
import UIKit
class BadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addbadgetobutton(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addbadgetobutton(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addbadgetobutton(badge: nil)
}
func addbadgetobutton(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addbadgetobutton(badge: nil)
fatalError("init(coder:) is not implemented")
}
}
Step 2:
Create a function in your base file which u can use in each View Controller :
func addBadge(itemvalue: String) {
let bagButton = BadgeButton()
bagButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
bagButton.tintColor = UIColor.darkGray
bagButton.setImage(UIImage(named: "ShoppingBag")?.withRenderingMode(.alwaysTemplate), for: .normal)
bagButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
bagButton.badge = itemvalue
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: bagButton)
}
Step 3 :
Use above function from any View Controller in this way :
self.addBadge(itemvalue: localStorage.string(forKey: "total_products_in_cart") ?? "0")
First create label, then right bar button. On right bar button add subview which will be badge count. Finally add navigation right bar button.
SWIFT 5
let badgeCount = UILabel(frame: CGRect(x: 22, y: -05, width: 20, height: 20))
badgeCount.layer.borderColor = UIColor.clear.cgColor
badgeCount.layer.borderWidth = 2
badgeCount.layer.cornerRadius = badgeCount.bounds.size.height / 2
badgeCount.textAlignment = .center
badgeCount.layer.masksToBounds = true
badgeCount.textColor = .white
badgeCount.font = badgeCount.font.withSize(12)
badgeCount.backgroundColor = .red
badgeCount.text = "4"
let rightBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
rightBarButton.setBackgroundImage(UIImage(named: "NotificationBell"), for: .normal)
rightBarButton.addTarget(self, action: #selector(self.onBtnNotification), for: .touchUpInside)
rightBarButton.addSubview(badgeCount)
let rightBarButtomItem = UIBarButtonItem(customView: rightBarButton)
navigationItem.rightBarButtonItem = rightBarButtomItem
I had the same task. I didn't want to use third-party libraries. Firstly, I tried Stefano's solution and it's great however I decided to implement my own way to solve it.
In my humble opinion, there are simple steps described below briefly:
Create UIView instance within .xib file and put necessary items like UILabel or UIImageView instance depending on your design requirements.
The final action I did in this step is putting invisible button in the top of view's hierarchy.
Create YourCustomView.swift and link all #IBOutlets from xib to current file inside your custom view class implementation.
Next, implement class function in YourCustomView class which will load custom view from xib and return it as YourCustomView instance.
Finally, add your custom badge to your custom view controller instance!
My result is..
P.S. If you need to implement #IBActions I recommend to link your custom view and custom view controller through the delegate pattern.
using M13BadgeView.. use this code
(im using fontawesome.swift for buttons :: https://github.com/thii/FontAwesome.swift)
let rightButton = UIButton(frame: CGRect(x:0,y:0,width:30,height:30))
rightButton.titleLabel?.font = UIFont.fontAwesome(ofSize: 22)
rightButton.setTitle(String.fontAwesomeIcon(name: .shoppingBasket), for: .normal)
let rightButtonItem : UIBarButtonItem = UIBarButtonItem(customView: rightButton)
let badgeView = M13BadgeView()
badgeView.text = "1"
badgeView.textColor = UIColor.white
badgeView.badgeBackgroundColor = UIColor.red
badgeView.borderWidth = 1.0
badgeView.borderColor = UIColor.white
badgeView.horizontalAlignment = M13BadgeViewHorizontalAlignmentLeft
badgeView.verticalAlignment = M13BadgeViewVerticalAlignmentTop
badgeView.hidesWhenZero = true
rightButton.addSubview(badgeView)
self.navigationItem.rightBarButtonItem = rightButtonItem
Good answer #Julio Bailon (https://stackoverflow.com/a/45948819/1898973)!
Here is the author's site with full explanation: http://www.stefanovettor.com/2016/04/30/adding-badge-uibarbuttonitem/.
It seems not to be working on iOS 11, maybe because the script try to access the "view" property of the UIBarButtonItem. I made it work:
By creating a UIButton and then creating the UIBarButtonItem using the UIButton as a customView:
navigationItem.rightBarButtonItem = UIBarButtonItem.init(
customView: shoppingCartButton)
By replacing the line in the UIBarButtonItem extension:
guard let view = self.value(forKey: "view") as? UIView else { return }
with the following:
guard let view = self.customView else { return }
Seems elegant to me and, best of all, it worked!
You can set below constraints to UILabel with respect to UIButton
align UILabel's top and trailing to UIButton
And when you need to show badge set text to UILabel and when you don't want to show badge then set empty string to UILabel
Download This
For BarButtonItem : Drag and Drop UIBarButtonItem+Badge.h and UIBarButtonItem+Badge.m class in project.
Write this code for set Badges:
self.navigationItem.rightBarButtonItem.badgeValue = "2"
self.navigationItem.rightBarButtonItem.badgeBGColor = UIColor.black
For UIButtton : Drag and Drop UIButton+Badge.h and UIButton+Badge.m class in project.
self.notificationBtn.badgeValue = "2"
self.notificationBtn.badgeBGColor = UIColor.black
Answer with extension from Julio will not work.
Starting from iOS 11 this code will not work cause line of code below will not cast UIView. Also it's counting as private API and seems to be will not pass AppStore review.
guard let view = self.value(forKey: "view") as? UIView else { return }
Thread on Apple Developer Forum
Second thing that this snippet always draws circle, so it can't fit numbers bigger than 9.
Here the simplified version by using custom view
Easy and clear solution if you are looking for only adding the red dot without the number;
private var handle: UInt8 = 0;
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func setBadge(offset: CGPoint = .zero, color: UIColor = .red, filled: Bool = true, fontSize: CGFloat = 11) {
badgeLayer?.removeFromSuperlayer()
guard let view = self.value(forKey: "view") as? UIView else {
return
}
var font = UIFont.systemFont(ofSize: fontSize)
if #available(iOS 9.0, *) {
font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
//Size of the dot
let badgeSize = UILabel(frame: CGRect(x: 22, y: -05, width: 10, height: 10))
// initialize Badge
let badge = CAShapeLayer()
let height = badgeSize.height
let width = badgeSize.width
// x position is offset from right-hand side
let x = view.frame.width + offset.x - 17
let y = view.frame.height + offset.y - 34
let badgeFrame = CGRect(origin: CGPoint(x: x, y: y), size: CGSize(width: width, height: height))
badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// initialiaze Badge's label
let label = CATextLayer()
label.alignmentMode = .center
label.font = font
label.fontSize = font.pointSize
label.frame = badgeFrame
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// bring layer to front
badge.zPosition = 1_000
}
private func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
// MARK: - Utilities
extension CAShapeLayer {
func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
path = UIBezierPath(roundedRect: rect, cornerRadius: 7).cgPath
}
}
The source of the code:
https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4
I only made a few changes to eliminate the number.
The MIBadgeButton-Swift is working also on UIBarButtonItems.
Here is my code after the navigation bar is created:
let rightBarButtons = self.navigationItem.rightBarButtonItems
let alarmsBarButton = rightBarButtons?.last
let alarmsButton = alarmsBarButton.customView as! MIBadgeButton?
alarmsButton.badgeString = "10"
You can do it programmatically with
self.tabBarItem.badgeColor = .red
or use the storyboard. See:

Is it possible to customize UITabBarItem Badge?

The question below is similar as mine.
How to use a custom UIImage as an UITabBarItem Badge?
Is it possible to use a background image instead of drawing it myself?
I think it's fine since my app will only use a 1-digit badgeValue.
If it is not really possible, I want to know if we can change the badge color instead.
The answers in the question below are not really helping.
Is it possible to change UITabBarItem badge color
This is your best bet. Add this extension at the file scope and you can customise the badges however you like. Just call self.tabBarController!.setBadges([1,0,2]) in any of your root view controllers.
To be clear that is for a tab bar with three items, with the badge values going from left to right.
If you want to add images instead just change the addBadge method
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
var labelExistsForIndex = [Bool]()
for _ in badgeValues {
labelExistsForIndex.append(false)
}
for view in self.tabBar.subviews where view is PGTabBadge {
let badgeView = view as! PGTabBadge
let index = badgeView.tag
if badgeValues[index] == 0 {
badgeView.removeFromSuperview()
}
labelExistsForIndex[index] = true
badgeView.text = String(badgeValues[index])
}
for i in 0...(labelExistsForIndex.count - 1) where !labelExistsForIndex[i] && (badgeValues[i] > 0) {
addBadge(index: i, value: badgeValues[i], color: .red, font: UIFont(name: "Helvetica-Light", size: 11)!)
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let itemPosition = CGFloat(index + 1)
let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBar.items!.count)
let bgColor = color
let xOffset: CGFloat = 5
let yOffset: CGFloat = -12
let badgeView = PGTabBadge()
badgeView.frame.size = CGSize(width: 12, height: 12)
badgeView.center = CGPoint(x: (itemWidth * itemPosition) - (itemWidth / 2) + xOffset, y: 20 + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = bgColor
badgeView.tag = index
tabBar.addSubview(badgeView)
}
}
class PGTabBadge: UILabel { }
Here is another solution based on TimWhiting's answer:
extension UITabBar {
func setBadge(value: String?, at index: Int, withConfiguration configuration: TabBarBadgeConfiguration = TabBarBadgeConfiguration()) {
let existingBadge = subviews.first { ($0 as? TabBarBadge)?.hasIdentifier(for: index) == true }
existingBadge?.removeFromSuperview()
guard let tabBarItems = items,
let value = value else { return }
let itemPosition = CGFloat(index + 1)
let itemWidth = frame.width / CGFloat(tabBarItems.count)
let itemHeight = frame.height
let badge = TabBarBadge(for: index)
badge.frame.size = configuration.size
badge.center = CGPoint(x: (itemWidth * itemPosition) - (0.5 * itemWidth) + configuration.centerOffset.x,
y: (0.5 * itemHeight) + configuration.centerOffset.y)
badge.layer.cornerRadius = 0.5 * configuration.size.height
badge.clipsToBounds = true
badge.textAlignment = .center
badge.backgroundColor = configuration.backgroundColor
badge.font = configuration.font
badge.textColor = configuration.textColor
badge.text = value
addSubview(badge)
}
}
class TabBarBadge: UILabel {
var identifier: String = String(describing: TabBarBadge.self)
private func identifier(for index: Int) -> String {
return "\(String(describing: TabBarBadge.self))-\(index)"
}
convenience init(for index: Int) {
self.init()
identifier = identifier(for: index)
}
func hasIdentifier(for index: Int) -> Bool {
let has = identifier == identifier(for: index)
return has
}
}
class TabBarBadgeConfiguration {
var backgroundColor: UIColor = .red
var centerOffset: CGPoint = .init(x: 12, y: -9)
var size: CGSize = .init(width: 17, height: 17)
var textColor: UIColor = .white
var font: UIFont! = .systemFont(ofSize: 11) {
didSet { font = font ?? .systemFont(ofSize: 11) }
}
static func construct(_ block: (TabBarBadgeConfiguration) -> Void) -> TabBarBadgeConfiguration {
let new = TabBarBadgeConfiguration()
block(new)
return new
}
}
You can use a more robust solution #UITabbarItem-CustomBadge.
Demo
Simple two line of code can get you going
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//supplying the animation parameter
[UITabBarItem setDefaultAnimationProvider:[[DefaultTabbarBadgeAnimation alloc] init]];
[UITabBarItem setDefaultConfigurationProvider:[[DefaultSystemLikeBadgeConfiguration alloc] init]];
//rest of your code goes following...
return YES;
}
well... changing the background of the built-in badge does not seem possible to me. But what IS possible instead, is the following:
Subclass the TabBarController, build a custom view laid out in a NIB, add it to the view hierarchy at the location in the tab bar, where you want it to be.
In the custom view you can set up an image as background and a label on top of that background, that will display the number value you can then change by code.
Then you need to figure out the horizontal and vertical location of your custom view where you want to place your view inside of the tab bar.
Hope this helps a little bit.
Sebastian
TimWhiting's answer updated to Swift 4 with the removal of some force unwrapping.
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
var labelExistsForIndex = [Bool]()
for _ in badgeValues {
labelExistsForIndex.append(false)
}
for view in tabBar.subviews {
if let badgeView = view as? PGTabBadge {
let index = badgeView.tag
if badgeValues[index] == 0 {
badgeView.removeFromSuperview()
}
labelExistsForIndex[index] = true
badgeView.text = String(badgeValues[index])
}
}
for i in 0 ... labelExistsForIndex.count - 1 where !labelExistsForIndex[i] && badgeValues[i] > 0 {
addBadge(
index: i,
value: badgeValues[i],
color: UIColor(red: 4 / 255, green: 110 / 255, blue: 188 / 255, alpha: 1),
font: UIFont(name: "Helvetica-Light", size: 11)!
)
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
guard let tabBarItems = tabBar.items else { return }
let itemPosition = CGFloat(index + 1)
let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBarItems.count)
let bgColor = color
let xOffset: CGFloat = 12
let yOffset: CGFloat = -9
let badgeView = PGTabBadge()
badgeView.frame.size = CGSize(width: 17, height: 17)
badgeView.center = CGPoint(x: (itemWidth * itemPosition) - (itemWidth / 2) + xOffset, y: 20 + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width / 2
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = bgColor
badgeView.tag = index
tabBar.addSubview(badgeView)
}
}
class PGTabBadge: UILabel {}
Sample Image