How to add small red dot in UITabBarItem - ios

How to add red dot on the top right side of the UITabBarItem.
I have searched a while and some guys said this can be done setting Badge Value of the UITabBarItem.But when I give it a try and set badge value to empty space " ",the red dot is somewhat big.How can I get a proper one?Big thanks.

If you want to avoid traversing subviews & potentially dangerous hacks in general, what I've done is set the badge's background colour to clear and used a styled bullet point to appear as a badge:
tabBarItem.badgeValue = "●"
tabBarItem.badgeColor = .clear
tabBarItem.setBadgeTextAttributes([NSAttributedStringKey.foregroundColor.rawValue: UIColor.red], for: .normal)
This seems more future-proof than the other answers.

you can try this method:
func addRedDotAtTabBarItemIndex(index: Int) {
for subview in tabBarController!.tabBar.subviews {
if let subview = subview as? UIView {
if subview.tag == 1314 {
subview.removeFromSuperview()
break
}
}
}
let RedDotRadius: CGFloat = 5
let RedDotDiameter = RedDotRadius * 2
let TopMargin:CGFloat = 5
let TabBarItemCount = CGFloat(self.tabBarController!.tabBar.items!.count)
let HalfItemWidth = CGRectGetWidth(view.bounds) / (TabBarItemCount * 2)
let xOffset = HalfItemWidth * CGFloat(index * 2 + 1)
let imageHalfWidth: CGFloat = (self.tabBarController!.tabBar.items![index] as! UITabBarItem).selectedImage.size.width / 2
let redDot = UIView(frame: CGRect(x: xOffset + imageHalfWidth, y: TopMargin, width: RedDotDiameter, height: RedDotDiameter))
redDot.tag = 1314
redDot.backgroundColor = UIColor.redColor()
redDot.layer.cornerRadius = RedDotRadius
self.tabBarController?.tabBar.addSubview(redDot)
}

set the badgeValue for your desired UITabBarItem as follow:
// for first tab
(tabBarController!.tabBar.items!.first! as! UITabBarItem).badgeValue = "1"
//for second tab
(tabBarController!.tabBar.items![1] as! UITabBarItem).badgeValue = "2"
// for last tab
(tabBarController!.tabBar.items!.last! as! UITabBarItem).badgeValue = "final"
for remove a badge from the UITabBarItem just assign nil
(tabBarController!.tabBar.items!.first! as! UITabBarItem).badgeValue = nil
you can get the output Like
for additional information please ref this link
Choice --2
var lbl : UILabel = UILabel(frame: CGRectMake(225, 5, 20, 20))
lbl.layer.borderColor = UIColor.whiteColor().CGColor
lbl.layer.borderWidth = 2
lbl.layer.cornerRadius = lbl.bounds.size.height/2
lbl.textAlignment = NSTextAlignment.Center
lbl.layer.masksToBounds = true
lbl.font = UIFont(name: hereaddyourFontName, size: 13)
lbl.textColor = UIColor.whiteColor()
lbl.backgroundColor = UIColor.redColor()
lbl.text = "1" //if you no need remove this
// add subview to tabBarController?.tabBar
self.tabBarController?.tabBar.addSubview(lbl)
the output is

That is very simple in current iOS versions
tabBarItem.badgeValue = " "
it shows the red dot on the top of the tabbar item

Swift 5+
This goes into the controller that belongs to the tab
alt. you just need to grab the right tabBarItem
func updateTabBarBadge(showDot: Bool) {
guard let tbi = tabBarItem else {
return
}
if showDot {
tbi.setBadgeTextAttributes([.font: UIFont.systemFont(ofSize: 6), .foregroundColor:UIColor(named: "Primary")!], for: .normal)
tbi.badgeValue = "⬤"
tbi.badgeColor = UIColor.clear
} else {
tbi.badgeValue = nil
}
}

I have figured out a hack solution.
func addRedDotAtTabBarItemIndex(index: Int,dotRadius: CGFloat) {
var tabBarButtons = [UIView]()
// find the UITabBarButton instance.
for subview in tabBarController!.tabBar.subviews.reverse() {
if subview.isKindOfClass(NSClassFromString("UITabBarButton")) {
tabBarButtons.append(subview as! UIView)
}
}
if index >= tabBarButtons.count {
println("out of bounds")
return
}
let tabBar = tabBarButtons[index]
var selectedImageWidth: CGFloat!
var topMargin: CGFloat!
for subview in tabBar.subviews {
if subview.isKindOfClass(NSClassFromString("UITabBarSwappableImageView")) {
selectedImageWidth = (subview as! UIView).frame.size.width
topMargin = (subview as! UIView).frame.origin.y
}
}
// remove existing red dot.
for subview in tabBar.subviews {
if subview.tag == 999 {
subview.removeFromSuperview()
}
}
let redDot = UIView(frame: CGRect(x: CGRectGetMidX(tabBar.bounds) + selectedImageWidth / 2 + dotRadius, y: topMargin, width: dotRadius * 2, height: dotRadius * 2))
redDot.backgroundColor = UIColor.redColor()
redDot.layer.cornerRadius = dotRadius // half of the view's height.
redDot.tag = 999
tabBar.addSubview(redDot)
}

Works both for iPad and iPhone.
Be able to hide and calculate index automatically.
Call self.setTabBarDotVisible(visible:true) if self is not an UITabBarController.
Call self.setTabBarDotVisible(visible:true, index:2) if self is an UITabBarController.
import UIKit
public extension UIViewController {
func setTabBarDotVisible(visible:Bool,index: Int? = nil) {
let tabBarController:UITabBarController!
if self is UITabBarController
{
tabBarController = self as! UITabBarController
}
else
{
if self.tabBarController == nil
{
return
}
tabBarController = self.tabBarController!
}
let indexFinal:Int
if (index != nil)
{
indexFinal = index!
}
else
{
let index3 = tabBarController.viewControllers?.index(of: self)
if index3 == nil
{
return;
}
else
{
indexFinal = index3!
}
}
guard let barItems = tabBarController.tabBar.items else
{
return
}
//
let tag = 8888
var tabBarItemView:UIView?
for subview in tabBarController.tabBar.subviews {
let className = String(describing: type(of: subview))
guard className == "UITabBarButton" else {
continue
}
var label:UILabel?
var dotView:UIView?
for subview2 in subview.subviews {
if subview2.tag == tag {
dotView = subview2;
}
else if (subview2 is UILabel)
{
label = subview2 as? UILabel
}
}
if label?.text == barItems[indexFinal].title
{
dotView?.removeFromSuperview()
tabBarItemView = subview;
break;
}
}
if (tabBarItemView == nil || !visible)
{
return
}
let barItemWidth = tabBarItemView!.bounds.width
let x = barItemWidth * 0.5 + (barItems[indexFinal].selectedImage?.size.width ?? barItemWidth) / 2
let y:CGFloat = 5
let size:CGFloat = 10;
let redDot = UIView(frame: CGRect(x: x, y: y, width: size, height: size))
redDot.tag = tag
redDot.backgroundColor = UIColor.red
redDot.layer.cornerRadius = size/2
tabBarItemView!.addSubview(redDot)
}
}

i test this question's answer. but not work on iPad.
now i found that, when u add this on iPhone, tabBarItem left and right margin is 2, and each items margin is 4. Code as below:
NSInteger barItemCount = self.tabBar.items.count;
UITabBarItem *barItem = (UITabBarItem *)self.tabBar.items[index];
CGFloat imageHalfWidth = barItem.image.size.width / 2.0;
CGFloat barItemWidth = (BXS_WINDOW_WIDTH - barItemCount * 4) / barItemCount;
CGFloat barItemMargin = 4;
CGFloat redDotXOffset = barItemMargin / 2 + barItemMargin * index + barItemWidth * (index + 0.5);
and iPad as below:
barItemWidth = 76;
barItemMargin = 34;
redDotXOffset = (BXS_WINDOW_WIDTH - 76 * barItemCount - 34 * (barItemCount - 1)) / 2.0 + 76 * (index + 0.5) + 34 * index;
Hope this is useful.

This it Swift 4 solution:
1) Add BaseTabBar custom class to your project:
import UIKit
class BaseTabBar: UITabBar {
static var dotColor: UIColor = UIColor.red
static var dotSize: CGFloat = 4
static var dotPositionX: CGFloat = 0.8
static var dotPositionY: CGFloat = 0.2
var dotMap = [Int: Bool]()
func resetDots() {
dotMap.removeAll()
}
func addDot(tabIndex: Int) {
dotMap[tabIndex] = true
}
func removeDot(tabIndex: Int) {
dotMap[tabIndex] = false
}
override func draw(_ rect: CGRect) {
super.draw(rect)
if let items = items {
for i in 0..<items.count {
let item = items[i]
if let view = item.value(forKey: "view") as? UIView, let dotBoolean = dotMap[i], dotBoolean == true {
let x = view.frame.origin.x + view.frame.width * BaseTabBar.dotPositionX
let y = view.frame.origin.y + view.frame.height * BaseTabBar.dotPositionY
let dotPath = UIBezierPath(ovalIn: CGRect(x: x, y: y, width: BaseTabBar.dotSize, height: BaseTabBar.dotSize))
BaseTabBar.dotColor.setFill()
dotPath.fill()
}
}
}
}
}
2) Change the custom class of UITabBar inside your UITabBarController to BaseTabBar.
3) Manage the dots in the place where you can access the tabBarController
func updateNotificationCount(count: Int) {
if let tabBar = navigationController?.tabBarController?.tabBar as? BaseTabBar {
if count > 0 {
tabBar.addDot(tabIndex: 0)
} else {
tabBar.removeDot(tabIndex: 0)
}
tabBar.setNeedsDisplay()
}
}

I added 5 tab bar indexes and add the dot points according to the notification occurs. First, create Dots view array.
var Dots = [UIView](repeating: UIView(), count: 5)
func addRedDotAtTabBarItemIndex(index: Int) {
if self.Dots[index].tag != index {
let RedDotRadius: CGFloat = 7
let RedDotDiameter = RedDotRadius
let TopMargin:CGFloat = 2
let tabSize = self.tabBarController.view.frame.width / CGFloat(5)
let xPosition = tabSize * CGFloat(index - 1)
let tabHalfWidth: CGFloat = tabSize / 2
self.Dots[index] = UIView(frame: CGRect(x: xPosition + tabHalfWidth - 2 , y: TopMargin, width: RedDotDiameter, height: RedDotDiameter))
self.Dots[index].tag = index
self.Dots[index].backgroundColor = UIColor.red
self.Dots[index].layer.cornerRadius = RedDotRadius
self.tabBarController.tabBar.addSubview(self.Dots[index])
}
}
If you want to remove the dot from selected index, use this code:
func removeRedDotAtTabBarItemIndex(index: Int) {
self.Dots[index].removeFromSuperview()
self.Dots[index].tag = 0
}

simple solution
set space in storyboard tabbaritem badge value.
if we add space below output you can get:

In Swift 5:
tabBarItem.badgeValue = "1"
to change from default color use:
tabBarItem.badgeColor = UIColor.systemBlue

From iOS 13, use UITabBarAppearance and UITabBarItemAppearance
let appearance = UITabBarAppearance()
let itemAppearance = UITabBarItemAppearance(style: .stacked)
itemAppearance.normal.badgeBackgroundColor = .clear
itemAppearance.normal.badgeTextAttributes = [.foregroundColor: UIColor.red]
profileViewController.tabBarItem.badgeValue = "●"

Related

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

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

Glitchy UIScrollView Infinite Scroll

Im trying to implement an infinitely paging UIScrollView based on the Advanced ScrollView Techniques from WWDC 2011. The problem that Im facing is that as I scroll the screen keeps on Jumping backwards instead of advancing forward in the array. Is there any way to create this effect. Below is the code I have implemented thus far.
import Foundation
import UIKit
class CustomScrollView:UIScrollView{
var label1:CustomLabel!
var label2:CustomLabel!
var label3:CustomLabel!
var labels:[UILabel]!
var visibleLabels:[UILabel]!
var recycledPages:Set<CustomLabel>!
var visiblePages:Set<CustomLabel>!
override init(frame: CGRect) {
super.init(frame: frame)
indicatorStyle = .white
recycledPages = Set<CustomLabel>()
visiblePages = Set<CustomLabel>()
var firstScreenPostion:CGRect = CGRect(origin: CGPoint(x: 0 * bounds.width, y: 0), size: bounds.size)
var secondeScreenPosition:CGRect = CGRect(origin: CGPoint(x: 1 * bounds.width, y: 0), size: bounds.size)
var thirdScreenPosition:CGRect = CGRect(origin: CGPoint(x: 2 * bounds.width, y: 0), size: bounds.size)
label1 = CustomLabel(frame: firstScreenPostion)
label1.backgroundColor = .red
label1.text = "1"
label2 = CustomLabel(frame: secondeScreenPosition)
label2.backgroundColor = .green
label2.text = "2"
label3 = CustomLabel(frame: thirdScreenPosition)
label3.backgroundColor = .blue
label3.text = "3"
visibleLabels = [label1,label2,label3]
labels = [label1,label2,label3]
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func recenterIfNecessary(){
let currentOffset = contentOffset
let contentWidth = contentSize.width
let centerOffset = (contentWidth - bounds.width)/2
let distanceFromCenter = abs(currentOffset.x - centerOffset)
if distanceFromCenter > contentWidth/4{
self.contentOffset = CGPoint(x: centerOffset, y: 0)
for label in visibleLabels{
var center = label.center
center.x += centerOffset - currentOffset.x
label.center = center
}
}
}
override func layoutSubviews() {
recenterIfNecessary()
let visibleBounds = bounds
let minimumVisibleX = bounds.minX
let maximumVisbleX = bounds.maxX
tilePages(minimumVisibleX: minimumVisibleX, toMaxX: maximumVisbleX)
// tileLabelsFromMinX(minimumVisibleX: minimumVisibleX, toMaxX: maximumVisbleX)
}
func insertLabel()->UILabel{
let recycledLabels = visibleLabels.filter { (label) -> Bool in
return label.superview == nil
}
let label = recycledLabels.last ?? UILabel(frame: bounds)
self.addSubview(label)
return label
}
func placeNewLabelOnRight(rightEdge: CGFloat)->CGFloat{
let label = self.insertLabel()
visibleLabels.append(label) // add rightmost label at the end of the array
label.frame.origin.x = rightEdge;
label.frame.origin.y = 0
label.text = labels.last?.text
return label.frame.maxX
}
func placeNewLabelOnLeft(leftEdge:CGFloat)->CGFloat{
let label = self.insertLabel()
self.visibleLabels.insert(label, at: 0) // add leftmost label at the beginning of the array
label.frame.origin.x = leftEdge - frame.size.width;
label.frame.origin.y = bounds.size.height - frame.size.height;
label.text = labels[0].text
return label.frame.minX
}
//function used in video
// func tileLabelsFromMinX(minimumVisibleX:CGFloat, toMaxX maximumVisibleX:CGFloat){
// // the upcoming tiling logic depends on there already being at least one label in the visibleLabels array, so
// // to kick off the tiling we need to make sure there's at least one label
// if (self.visibleLabels.count == 0)
// {
// self.placeNewLabelOnRight(rightEdge: minimumVisibleX);
// }
// print("visible labels.count: \(visibleLabels.count)")
//
// // add labels that are missing on right side
// // UILabel *lastLabel = [self.visibleLabels lastObject];
// var lastLabel = visibleLabels.last!
// var rightEdge = lastLabel.frame.maxX
// while (rightEdge < maximumVisibleX){
// rightEdge = self.placeNewLabelOnRight(rightEdge: rightEdge)
// }
//
// // add labels that are missing on left side
// var firstLabel = self.visibleLabels[0]
// var leftEdge = firstLabel.frame.minX
// while (leftEdge > minimumVisibleX){
// leftEdge = self.placeNewLabelOnLeft(leftEdge:leftEdge)
// }
//
// // remove labels that have fallen off right edge
// // lastLabel = [self.visibleLabels lastObject];
//
// while (lastLabel.frame.origin.x > maximumVisibleX){
// lastLabel.removeFromSuperview()
// self.visibleLabels.removeLast()
// lastLabel = self.visibleLabels.last!
// }
//
// // remove labels that have fallen off left edge
// firstLabel = self.visibleLabels[0];
// while (firstLabel.frame.maxX < minimumVisibleX){
// firstLabel.removeFromSuperview()
// self.visibleLabels.removeFirst()
// firstLabel = self.visibleLabels[0];
// }
// }
func tilePages(minimumVisibleX:CGFloat, toMaxX maximumVisibleX:CGFloat){
let visibleBounds = bounds
var firstNeededPageIndex:Int = Int(floorf(Float(minimumVisibleX/visibleBounds.width)))
var lastNeededPageIndex:Int = Int(floorf(Float((maximumVisibleX - 1)/visibleBounds.width)))
firstNeededPageIndex = max(firstNeededPageIndex, 0)
lastNeededPageIndex = min(lastNeededPageIndex, labels.count - 1)
//Recycle no-longer needed pages
for page in visiblePages{
if page.index < Int(firstNeededPageIndex) || page.index > Int(lastNeededPageIndex){
recycledPages.insert(page)
page.removeFromSuperview()
}
}
visiblePages.subtract(recycledPages)
//add missing pages
for i in firstNeededPageIndex...lastNeededPageIndex{
if !isDisplaying(pageForIndex: i){
let page:CustomLabel = dequeueRecycledPage() ?? CustomLabel()
print("index i: \(i)")
self.configurePage(page: page, forIndex: i)
self.addSubview(page)
visiblePages.insert(page)
}
}
}
func isDisplaying(pageForIndex index:Int)->Bool{
for page in visiblePages{
if page.index == index{
return true
}
}
return false
}
func configurePage(page:CustomLabel,forIndex index:Int){
page.index = index
page.text = "current index: \(index)"
let width = bounds.width
let newX:CGFloat = CGFloat(index) * width
page.backgroundColor = labels[index].backgroundColor
page.frame = CGRect(origin: CGPoint(x: newX, y: 0), size: bounds.size)
}
func dequeueRecycledPage()->CustomLabel?{
let page = recycledPages.first
if let page = page{
recycledPages.remove(page)
return page
}
return nil
}
}

Matching Row Height Between UITableviews

My application is to display employee schedules at a store.
My design is that on the left side I have a tableview with employee names in each cell, on the right side I have a header with store operation times and below that a colored bar that extends from an employee's start time to their end time.
The right side of the table must scroll horizontally to so that the user can scroll and see the schedule for any part of the day. I accomplished this effect by putting the times header and the right side table into a scrollview.
In the heightForRowAt function I have
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if tableView.accessibilityIdentifier == "SecondaryTable" {
var height = tblView.rowHeight
if let nameCell = tblView.cellForRow(at: indexPath) {
height = nameCell.frame.height
} else {
height = tblView.rowHeight
}
return height
}
else {
return UITableView.automaticDimension
}
}
Which for the name table returns UITableView.automaticDimension and for the hours table (accessibility identifier "SecondaryTable") should return the height for the corresponding cell on the name table to make sure they line up properly.
The issue is for some reason heightForRowAt is being called for the schedule table when the name table has not loaded the corresponding cell and so it returns tblView.rowHeight which is not the correct height. You can see this in the second to last row in the above image. I verified this by checking what indexPath the schedule table was loading and that the index was not in the list of visible cells for the name table.
The only time these tables are being reloaded is in the viewWillAppear right now:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
self.tblView.reloadData()
self.secondTblView.reloadData()
}
}
This is only affecting the last row in the table when initially loaded but when the name table does load in the cell doesn't line up with the schedule. It looks like this corrects itself later on but doesn't reload the cell because the information in the cell doesn't change to fill the cell but the next cell starts at the correct spot and the separator lines line up properly.
In case it helps, here is my cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduleCell") as? ScheduleCell ?? ScheduleCell()
let cellSchedule = schedule[indexPath.row]
if tableView.accessibilityIdentifier == "SecondaryTable" {
let coloredLabel = UILabel()
coloredLabel.text = " " + cellSchedule.role
coloredLabel.backgroundColor = UIColor.green.darker(by: 35)
coloredLabel.textColor = UIColor.white
timesHeader.layoutSubviews()
var drawStartOnHalfHour = false
var drawEndOnHalfHour = false
var clockFormat = ClockFormat.TwentyFourHour
for hour in self.timesHeader.arrangedSubviews{
let lineLayer = CAShapeLayer()
let x = hour.center.x
switch indexPath.row % 2 {
case 0:
lineLayer.strokeColor = UIColor.lightGray.cgColor
break
default:
lineLayer.strokeColor = UIColor.white.cgColor
break
}
lineLayer.lineWidth = 2
let path = CGMutablePath()
if let header = hour as? UILabel, header.text != nil {
lineLayer.lineDashPattern = [1,5]
path.addLines(between: [CGPoint(x: x, y: 5),
CGPoint(x: x, y: cell.contentView.frame.maxY - 5)])
} else {
path.addLines(between: [CGPoint(x: x, y: cell.contentView.frame.maxY/2 - 2),
CGPoint(x: x, y: cell.contentView.frame.maxY/2 + 2)])
}
lineLayer.path = path
DispatchQueue.main.async {
cell.contentView.layer.addSublayer(lineLayer)
}
if let header = hour as? UILabel {
if header.text != nil {
var afterNoon = false
//On the hour logic
var formatCheck = ""
if header.text!.split(separator: ":").count == 1 {
clockFormat = .TwelveHour
formatCheck = String(describing: header.text!.split(separator: " ")[1] )
}
var headerTime = 0
if clockFormat == .TwentyFourHour {
headerTime = Int(String(describing: header.text!.split(separator: ":")[0])) ?? 0
} else {
headerTime = Int(String(describing: header.text!.split(separator: " ")[0])) ?? 0
}
var UTCCalendar = Calendar.current
UTCCalendar.timeZone = TimeZone(abbreviation: "UTC")!
var t = UTCCalendar.component(.hour, from: cellSchedule.StartTime)
if clockFormat == .TwelveHour && t >= 12{
if t > 12 {
t = t-12
}
afterNoon = true
}
if headerTime == t {
let m = UTCCalendar.component(.minute, from: cellSchedule.StartTime)
if clockFormat == .TwentyFourHour || ((afterNoon && formatCheck.contains("p")) || (!afterNoon && formatCheck.contains("a"))) {
if m == 0 {
//Logic for start times on the hour
coloredLabel.frame = CGRect(x: x, y: cell.contentView.frame.maxY/4,
width: 5, height: cell.contentView.frame.maxY/2)
} else {
//Logic for handling start times on half-hour
drawStartOnHalfHour = true
}
}
}
var e = UTCCalendar.component(.hour, from: cellSchedule.EndTime)
if clockFormat == .TwelveHour && e >= 12{
if e > 12 {
e = e - 12
}
afterNoon = true
}
if headerTime == e {
let m = UTCCalendar.component(.minute, from: cellSchedule.EndTime)
if clockFormat == .TwentyFourHour || ((afterNoon && formatCheck.contains("p")) || (!afterNoon && formatCheck.contains("a"))) {
if m == 0 {
//Logic for end time on the hour
let width = x - coloredLabel.frame.minX
coloredLabel.frame = CGRect(x: coloredLabel.frame.minX,
y: coloredLabel.frame.minY,
width: width, height: coloredLabel.frame.height)
} else {
//Logic for end time on the half-hour
drawEndOnHalfHour = true
}
}
}
} else {
//Half-hour logic
if drawStartOnHalfHour {
drawStartOnHalfHour = false
coloredLabel.frame = CGRect(x: x, y: cell.contentView.frame.maxY/4,
width: 5, height: cell.contentView.frame.maxY/2)
} else if drawEndOnHalfHour {
drawEndOnHalfHour = false
let width = x - coloredLabel.frame.minX
coloredLabel.frame = CGRect(x: coloredLabel.frame.minX,
y: coloredLabel.frame.minY,
width: width, height: coloredLabel.frame.height)
}
}
}
}
DispatchQueue.main.async {
cell.contentView.addSubview(coloredLabel)
}
switch indexPath.row % 2 {
case 0:
let backGround = CALayer()
backGround.frame = cell.contentView.frame
backGround.backgroundColor = UIColor.white.cgColor
cell.contentView.layer.addSublayer(backGround)
break
default:
let backGround = CALayer()
backGround.frame = CGRect(x: 0,
y: 0,
width: self.timesHeader.frame.width,
height: cell.contentView.frame.height)
backGround.backgroundColor = UIColor.lightGray.cgColor
cell.contentView.layer.addSublayer(backGround)
break
}
} else {
cell.textLabel?.numberOfLines = 2
let firstName = String(describing: cellSchedule.FirstName!.prefix(35))
let lastName = String(describing: cellSchedule.LastName!.prefix(35))
cell.textLabel?.text = firstName.trimmingCharacters(in: CharacterSet(charactersIn: " ")) + "\n" + lastName.trimmingCharacters(in: CharacterSet(charactersIn: " "))
cell.textLabel?.sizeToFit()
switch indexPath.row % 2 {
case 0:
cell.textLabel?.textColor = UIColor.black
cell.contentView.backgroundColor = UIColor.white
break
default:
cell.textLabel?.textColor = UIColor.white
cell.contentView.backgroundColor = UIColor.lightGray
break
}
}
return cell
}
Any idea on a clean way to accomplish this?

UITapGestureRecognizer doesn't work properly

I made the function updateItems() which create, from an array, many UIView's in a UIScrollView :
Here is the file where this function is :
class MainViewController: UIViewController {
#IBOutlet weak var body: UIScrollView!
#IBOutlet weak var edit: UIButton!
var _title: String = "Title"
var _isEditing: Bool = false
var firstItems: [UISectionView] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.topItem?.title = self._title
navigationController?.navigationItem.largeTitleDisplayMode = .automatic
body.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height + 100)
self.updateItems(self.firstItems)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func updateItems(_ s: [UISectionView]) {
let topMargin = 10
let rightMargin = 10
let leftMargin = 10
let space = 5
let heightItem = 60
var b = topMargin
for i in body.subviews {
i.removeFromSuperview()
}
for t in s {
if t.isHidden == true {
continue
}
if t.title != nil {
let f = UIFont(name: "Helvetica", size: 20)
let l = UILabel(frame: CGRect(x: rightMargin, y : b, width: Int(UIScreen.main.bounds.width) - (rightMargin + leftMargin), height: Int(f!.lineHeight)))
l.font = f
l.text = t.title
body.addSubview(l)
b = b + Int(f!.lineHeight) + space
}
for i in t.items{
body.addSubview(i.getView(frame: CGRect(x: rightMargin, y: b, width: Int(UIScreen.main.bounds.width) - (rightMargin + leftMargin), height: heightItem), view: self))
b = b + heightItem + space
}
}
}
}
TIPS : UISectionView is an object which contains an array of UIItemView
The object UIItemView looks like :
class UIItemView {
var icon: UIImage = UIImage();
var line1: rString = rString("")!;
var line2: rString = rString("")!;
var leftline: Any = String();
var background: String = "white"
var onItemTap: (_ sender: UITapGestureRecognizer?) -> () = {sender in }
var onItemLongPress: (_ sender: UILongPressGestureRecognizer?) -> () = {sender in }
var id: String
init?(_ id: String) {
self.id = id
}
public func getView(frame: CGRect, view: UIViewController) -> UIView {
let width = Int(frame.width)
let height = Int(frame.height)
let rightMargin = 20
let leftMargin = 10
let topMargin = 10
let bottomMargin = 10
let iconSide = height - (topMargin + bottomMargin)
let marginLine = leftMargin + iconSide + 10
let v = UIView(frame: frame)
//Background & shape
if self.background == "white" {
v.backgroundColor = UIColor.white;
} else if self.background == "blur" {
let bEV = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.extraLight))
bEV.frame = v.bounds
bEV.autoresizingMask = [.flexibleWidth, .flexibleHeight]
v.addSubview(bEV)
}
v.layer.cornerRadius = 10.0
//Icon
let i = UIImageView()
i.image = self.icon;
i.frame = CGRect(x: leftMargin, y: topMargin, width: iconSide, height: iconSide)
v.addSubview(i)
//First Line
let l1 = self.line1.getLabel()
l1.frame = CGRect(x: marginLine, y: topMargin, width: width - (marginLine + leftMargin), height: Int(self.line1.getFont().lineHeight))
v.addSubview(l1)
//Seconde Line
let l2 = self.line2.getLabel()
l2.frame = CGRect(x: marginLine, y: height - (bottomMargin + Int(self.line1.getFont().lineHeight)), width: width - (marginLine + leftMargin), height: Int(self.line1.getFont().lineHeight))
v.addSubview(l2)
//Left Line
if type(of: self.leftline) == type(of: SpinnerView()) {
let sv = (self.leftline as! SpinnerView)
sv.frame = CGRect(x: width - (rightMargin + iconSide), y: height/2 - iconSide/2, width: iconSide, height: iconSide)
v.addSubview(sv)
} else if type(of: self.leftline) == type(of: rString("")) {
let rS = (self.leftline as! rString)
if rS.text != "" {
rS.fontName = "HelveticaNeue-Bold"
rS.size = 15
rS.color = UIColor(red:0.01, green:0.48, blue:1.00, alpha:1.0)
let l3 = rS.getLabel()
l3.frame = CGRect(x: width - (rightMargin + Int(rS.getFont().lineWidth(rS.text)) + 15), y: height/2 - (Int(rS.getFont().lineHeight) + 10)/2, width: Int(rS.getFont().lineWidth(rS.text)) + 15, height: Int(rS.getFont().lineHeight) + 10)
l3.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.97, alpha:1.0)
l3.layer.masksToBounds = true
l3.layer.borderWidth = 2
l3.layer.borderColor = UIColor(red:0.94, green:0.94, blue:0.97, alpha:1.0).cgColor
l3.layer.cornerRadius = rS.getFont().lineHeight/1.2
l3.textAlignment = .center
v.addSubview(l3)
}
}
//GestureRecognizer
v.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.oIT(_:))))
v.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.oILP(_:))))
v.restorationIdentifier = self.id
return v;
}
#objc func oIT(_ sender: UITapGestureRecognizer) {
print("Tap")
self.onItemTap(sender)
}
#objc func oILP(_ sender: UILongPressGestureRecognizer) {
print("LongPress")
self.onItemLongPress(sender)
}
static func ==(lhs: UIItemView, rhs: UIItemView) -> Bool {
return lhs === rhs
}
}
TIPS : UIItemView contains the function getView() which returns a specific UIView
The problem :
Everything work properly, when I load the ViewController (where there is the UIScrollView) every UIView's are build like I want, and I can interact with the UIView by the UITapGestureRecognizer or the UILongPressGestureRecognizer (the function is called as expected)
BUT
When I call the function updateItems() a second time, without reload the ViewController, the items change as expected but the UITapGestureRecognizer and the UILongPressGestureRecognizer don't work any more.
I hope you can help me :D
If information are missing for you to understand the problem, please let me know ;)

Smiley Rating Bar swift

If we had to do this Smiley Rating Bar on iOS...how we can do?
In the link-example, use a gif, but let's avoid this
If I had to do it ... I would use 5 images of faces for the background with their respective descriptions.
For the face that moves in its position X would use UIPanGestureRecognizer:
class ViewController: UIViewController , UIGestureRecognizerDelegate , UITextFieldDelegate{
#IBOutlet weak var image1: UIImageView!
var panGesture = UIPanGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
panGesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.draggedView(_:)))
image1.isUserInteractionEnabled = true
image1.addGestureRecognizer(panGesture)
}
func draggedView(_ sender:UIPanGestureRecognizer){
self.view.bringSubview(toFront: image1)
let translation = sender.translation(in: self.view)
image1.center = CGPoint(x: image1.center.x + translation.x, y: image1.center.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}
The question I have is how do I move the image1 to detect what is going on "above" the images below. Like this:
So...any help I will appreciate
I have done the same thing, Please use
https://github.com/gali8/G8SliderStep/tree/master/G8SliderStep Library .Please replace the Draw labels and Draw Images method with following in G8SliderStep.swift File.
#objc internal func drawLabels() {
guard let ti = tickTitles else {
return
}
if _stepTickLabels == nil {
_stepTickLabels = []
}
if let sl = _stepTickLabels {
for l in sl {
l.removeFromSuperview()
}
_stepTickLabels?.removeAll()
for index in 0..<ti.count {
let title = ti[index]
let lbl = UILabel()
lbl.font = unselectedFont
lbl.text = title
lbl.textAlignment = .center
lbl.sizeToFit()
var offset: CGFloat = 0
if index+1 < (steps%2 == 0 ? steps/2+1 : steps/2) {
offset = trackLeftOffset/2
}
else if index+1 > (steps%2 == 0 ? steps/2+1 : steps/2) {
offset = -(trackRightOffset/2)
}
if index == 0 {
offset = trackLeftOffset
}
if index == steps {
offset = -trackRightOffset
}
let x = offset + CGFloat(Double(index) * stepWidth) - (lbl.frame.size.width / 2)
var rect = lbl.frame
rect.origin.x = x
rect.origin.y = bounds.midY - (bounds.size.height / 2) - rect.size.height + 80
lbl.frame = rect
self.addSubview(lbl)
_stepTickLabels?.append(lbl)
}
}
}
#objc internal func drawImages() {
guard let ti = tickImages else {
return
}
if _stepTickImages == nil {
_stepTickImages = []
}
if let sl = _stepTickImages {
for l in sl {
l.removeFromSuperview()
}
_stepTickImages?.removeAll()
for index in 0..<ti.count {
let img = ti[index]
let imv = UIImageView(image: img)
imv.contentMode = .scaleAspectFit
imv.sizeToFit()
var offset: CGFloat = 0
if index+1 < (steps%2 == 0 ? steps/2+1 : steps/2) {
offset = trackLeftOffset/2
}
else if index+1 > (steps%2 == 0 ? steps/2+1 : steps/2) {
offset = -(trackLeftOffset/2)
}
if index == 0 {
offset = trackLeftOffset
}
if index == steps {
offset = -trackRightOffset
}
let x = offset + CGFloat(Double(index) * stepWidth) - (imv.frame.size.width / 2)
var rect = imv.frame
rect.origin.x = x
rect.origin.y = bounds.midY - (bounds.size.height / 2)
imv.frame = rect
self.insertSubview(imv, at: 2) //index 2 => draw images below the thumb/above the line
_stepTickImages?.append(imv)
}
}
}
Please get selected and unselected emoticons image from your personal resource, and pass those image in array as done in given example.In your view controller in viewDidLoad please write this code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
sliderStepBar.stepImages = [UIImage(named:"terrible")!, UIImage(named:"bad")!, UIImage(named:"okay")!, UIImage(named:"good")!,UIImage(named:"great")!, ]
sliderStepBar.tickTitles = ["Terrible", "Bad", "Okay", "Good", "Great"]
sliderStepBar.tickImages = [#imageLiteral(resourceName: "unselectterrible"), #imageLiteral(resourceName: "unselectbad"), #imageLiteral(resourceName: "unselectokay"),#imageLiteral(resourceName: "unselectgood"),#imageLiteral(resourceName: "unselectgreat")]
sliderStepBar.minimumValue = 4
sliderStepBar.maximumValue = Float(sliderStepBar.stepImages!.count) + sliderStepBar.minimumValue - 1.0
sliderStepBar.stepTickColor = UIColor.clear
sliderStepBar.stepTickWidth = 40
sliderStepBar.stepTickHeight = 40
sliderStepBar.trackHeight = 5
sliderStepBar.value = 5
}
Enjoy the Smiley Rating.
Happy Coding.
I have created the same, Check the below image
Overview
Easy customization(Font, Colors, Images, Ticks, Height, Width, Rounded)
#IBInspectable
Tappable
Draggable
Swift 5.0 above
Xcode 11 above
Orientation support
Manual drag & drop the class
Find the GIT URL for code
SmileyRating

Resources