Drag items placed on UIStackView - ios

I have the code below, and need to drag the views so they can be repositioned. However, on drag, the view simply disappears. How can I make the views draggable while on top of the stack view?
Stack View:
let sView = UIStackView()
sView.axis = UILayoutConstraintAxis.Vertical
sView.distribution = .FillEqually
sView.alignment = UIStackViewAlignment.Center
sView.spacing = 15
sView.backgroundColor = UIColor.redColor()
sView.addArrangedSubview(view1)
sView.addArrangedSubview(view2)
sView.addArrangedSubview(view3)
sView.layoutMarginsRelativeArrangement = true
sView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(sView)
sView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
sView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true
The views; view1, view2, and view3 have similar code as below:
let view1 = UIView()
view1.frame = CGRectMake(30, 50, 50, 80)
view1.backgroundColor = UIColor.blueColor()
view1.layer.cornerRadius = 6
let gesture = UIPanGestureRecognizer(target: self, action: Selector("draggedView:"))
view1.addGestureRecognizer(gesture)
view1.userInteractionEnabled = true
view1.tag = 1
view1.heightAnchor.constraintEqualToConstant(85).active = true
view1.widthAnchor.constraintEqualToConstant(55).active = true
The draggedView function:
func draggedView(gesture: UIPanGestureRecognizer){
let loc = gesture.locationInView(self.view)
let gesturedView = gesture.view
gesturedView!.center = loc
}

Brian,
This code does what I think your looking to do. Obviously you need to go thru it carefully and change variable names, but I sure you up to it!
func draggedView(sender: UIPanGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.Began) {
self.source = editorSVB!.arrangedSubviews.indexOf(sender.view!)! as Int
}
if (sender.state == UIGestureRecognizerState.Changed) {
center = sender.view?.center
let translation = sender.translationInView(sender.view)
center = CGPointMake(center!.x + translation.x, center!.y + translation.y)
sender.view?.center = center!
sender .setTranslation(CGPointZero, inView: sender.view)
}
for blah in self.editorSVB!.arrangedSubviews {
let no = blah.frame.intersect((sender.view?.frame)!)
if (!no.origin.x.isInfinite) {
self.object = editorSVB!.arrangedSubviews.indexOf(blah)! as Int
if (self.object != self.source) {
print("self,object, self.object",self,object, self.source)
self.executable = self.object
}
}
}
}

Related

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

Programmatically UIPanGestureRecognizer not doing anything

I have the folling UIView with a gesture recognizer that calls a function like so:
let cardView: UIView = {
let cv = UIView()
cv.backgroundColor = .red
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.cornerRadius = 5
cv.layer.masksToBounds = true
cv.isUserInteractionEnabled = true
cv.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan)))
return cv
}()
and the implementation of the gesture recognizer like this:
// pan functionality
func handlePan(gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.view)
if let card = gesture.view {
card.center = CGPoint(x: card.center.x + translation.x, y: card.center.y + translation.y)
}
}
However, when I try to move the card in the view controller nothing happens.
Any and all help is greatly appreciated.

Unable To Add Gesture on Dynamically Added UILabel

I am trying to add gesture on a dynamically created label but its not working this the code but its not working.
what am i doing wrong ?
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.center = CGPoint(x: 160, y: 285)
label.textAlignment = .center
label.text = field.text
label.isZoomEnabled = true;
label.minFontSize = 10;
label.maxFontSize = 80;
label.adjustsFontSizeToFitWidth = true;
label.setNeedsLayout()
label.isUserInteractionEnabled = true
let panRecognizer = UITapGestureRecognizer(target: self, action:Selector(("handlePan:")))
let rotateRecognizer = UITapGestureRecognizer(target: self, action:Selector(("handleRotate:")))
panRecognizer.delegate = self
rotateRecognizer.delegate = self
label.addGestureRecognizer(panRecognizer)
label.addGestureRecognizer(rotateRecognizer)
self.view.addSubview(label);
self.imagePicked.addSubview(label)
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint(x:0,y:0), in: self.view)
}
#IBAction func handleRotate(recognizer : UIRotationGestureRecognizer) {
if let view = recognizer.view {
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
}
Thanks In Advance.
You are creating UITapGestureRecognizer but the methods were taking UIPanGestureRecognizer and UIRotationGestureRecognizer. Also changed the selector so the methods are called properly and changed IBActions to methods.
Replace your code with this and it will work fine,
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.center = CGPoint(x: 160, y: 285)
label.textAlignment = .center
label.text = field.text
label.isZoomEnabled = true;
label.minFontSize = 10;
label.maxFontSize = 80;
label.adjustsFontSizeToFitWidth = true;
label.setNeedsLayout()
label.isUserInteractionEnabled = true
let panRecognizer = UIPanGestureRecognizer(target: self, action:#selector(self.handlePan))
let rotateRecognizer = UIRotationGestureRecognizer(target: self, action:#selector(self.handleRotate))
panRecognizer.delegate = self
rotateRecognizer.delegate = self
label.addGestureRecognizer(panRecognizer)
label.addGestureRecognizer(rotateRecognizer)
self.view.addSubview(label);
self.imagePicked.addSubview(label)
func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint(x:0,y:0), in: self.view)
}
func handleRotate(recognizer : UIRotationGestureRecognizer) {
if let view = recognizer.view {
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
}
It's the problem of UIlabel's superView.
I assume the imagePicked is an instance of UIImageView, and the default value of UIImageView's isUserInteractionEnabled is flase.
So the imagePicked and all of it's subviews cannot receive touch events.
To fix this problem, enbale the isUserInteractionEnabled property of UIImageView:
self.imagePicked.isUserInteractionEnabled = true;

I want to change the sidebar direction using UIView

I currently have a menu (SideBar) that reveals upon swipe and I want the menu to slide from right to left. I changed the swipe direction to match but I'm not sure how to change the SideBar frame or animation. How can I make the SideBar come from the right instead of left?
//
// SideBar.swift
// SideBarMenu
//
// Created by Alexandre on 30/01/2015.
// Copyright (c) 2015 Alexandre. All rights reserved.
//
import UIKit
//Necessary if we want to specify orptional requirements
#objc protocol SideBarDelegate{
func sideBarDidSelectButtonAtIndex(index:Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
}
//When an item of the sidebar is selected, and also when the sidebar will open or close
class SideBar: NSObject, SideBarTableViewControllerDelegate {
let barWidth:CGFloat = 150.0
let sideBarTableViewTopInset:CGFloat = 64.0
let sideBarContainerView:UIView = UIView()
let sideBarTableViewController:SideBarTableViewController = SideBarTableViewController()
var originView:UIView!
var animator:UIDynamicAnimator!
var delegate:SideBarDelegate!
var isSideBarOpen:Bool = false
//This init only allocate memory
override init(){
super.init()
}
init(sourceView:UIView, menuItems:Array<String>){
super.init()
originView = sourceView
sideBarTableViewController.tableData = menuItems
setupSideBar()
animator = UIDynamicAnimator(referenceView: originView)
let showGestureRecognizer:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipe:")
showGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Left
originView.addGestureRecognizer(showGestureRecognizer)
let hideGestureRecognizer:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipe:")
hideGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
originView.addGestureRecognizer(hideGestureRecognizer)
}
func setupSideBar(){
sideBarContainerView.frame = CGRectMake(-barWidth - 1, originView.frame.origin.y, barWidth, originView.frame.size.height)
sideBarContainerView.backgroundColor = UIColor.clearColor()
sideBarContainerView.clipsToBounds = false
//Add the sideBar to the originView
originView.addSubview(sideBarContainerView)
//blur back of the ground
//let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
//blurView.frame = sideBarContainerView.bounds
//sideBarContainerView.addSubview(blurView)
//Setup the menu/tableView
sideBarTableViewController.delegate = self
sideBarTableViewController.tableView.frame = sideBarContainerView.bounds
sideBarTableViewController.tableView.clipsToBounds = false
sideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
sideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
sideBarTableViewController.tableView.scrollsToTop = false
sideBarTableViewController.tableView.contentInset = UIEdgeInsetsMake(sideBarTableViewTopInset, 0, 0, 0)
sideBarTableViewController.tableView.reloadData()
sideBarContainerView.addSubview(sideBarTableViewController.tableView)
}
func handleSwipe(recognizer:UISwipeGestureRecognizer){
if recognizer.direction == UISwipeGestureRecognizerDirection.Right {
showSideBar(false)
delegate?.sideBarWillClose?()
} else {
showSideBar(true)
delegate?.sideBarWillOpen?()
}
}
func showSideBar(shouldOpen:Bool){
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//The gravity modifies the open and close speed
let gravityX:CGFloat = (shouldOpen) ? 1 : -1
let magnitude:CGFloat = (shouldOpen) ? 20 : -20
let boundaryX:CGFloat = (shouldOpen) ? barWidth : -barWidth - 1
let gravity:UIGravityBehavior = UIGravityBehavior(items: [sideBarContainerView])
gravity.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravity)
let collisionBehavior:UICollisionBehavior = UICollisionBehavior(items: [sideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, originView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [sideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [sideBarContainerView])
sideBarBehavior.elasticity = -0.3
animator.addBehavior(sideBarBehavior)
}
func sideBarControlDidSelectRow(indexPath: NSIndexPath) {
delegate?.sideBarDidSelectButtonAtIndex(indexPath.row)
}
}
What you want to do is make your starting frame offscreen to the right instead of the left. You'll also need to change the gravity direction.
in setupSideBar, you'll want to change the first line:
sideBarContainerView.frame = CGRectMake(barWidth + 1, originView.frame.origin.y, barWidth, originView.frame.size.height)
// notice the change in the first value (x position)
// the width +1 puts it offscreen +1.
// Previously it was -width-1 which put it to the left
then, in showSideBar:
// just reversing the gravity
let gravityX:CGFloat = (shouldOpen) ? -1 : 1

How to add small red dot in UITabBarItem

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 = "●"

Resources