swift- Assigning the value to the custom button from subview - swift

Hi I'm trying to assign the particular subview in custom buttons in for loop it is working fine in objective c, but I'm facing problem in swift code.
objective c code:
HButton *view = nil; // hbutton class object
NSArray *subviews = [self.superview subviews]; // storing the subviews in subviews array
NSInteger i = 0; // for index
for (view in subviews)
{
if ([view isKindOfClass:[HButton class]])
{
HButton *check=[subviews objectAtIndex:i];
if (self.tag == check.tag)
{
self.titleLabel.text =#"hel";
self.backgroundColor =[UIColor orangeColor];
}
i++;
}
Swift Code:
var view1 :BTNCustom! = nil
var subviews : NSArray = self.superview.subviews
var ival : Int = 0;
for view1 : AnyObject in subviews // I'm getting the error in this line
{
if view1.isKindOfClass(BTNCustom)
{
var check : BTNCustom = subviews.objectAtIndex(ival) as BTNCustom
if (self.tag == check.tag)
{
self.titleLabel.text = "hel";
self.backgroundColor = UIColor.orangeColor()
}
ival++;
}
}

Can you be more clear what you are actually trying to do? The following is the closest thing I can imagine to what you are trying to do:
for view in superview.subviews {
if view.isKindOfClass(BTNCustom) && tag == view.tag {
titleLabel.text = "hel"
backgroundColor = UIColor.orangeColor()
}
}
However, it really looks like you are abusing tags and not really understanding the point of a type system.
Specific problems with your code:
1) var view1 :BTNCustom! = nil you are assigning nil to something that you are saying you want to be automatically unwrapped, why not just var view1: BTNCustom!, they should be the same but the latter is much more palatable.
2) You are trying to use view1 as your loop variable of a different type when you have already declared it to be BTNCustom. Why? Are you really using variable names like this? Do you have a limit on the number of characters you are allowed to use?
3) What is the point of using an extra integer to pull views out of the subview array when in fact that view is exactly the one you are already have a reference to? To be clear, in your Obj-C code, both view and check will always be identical whenever you actually use check.

Just trying to solved your error may this will help you!
for index in 1...subviews
{
let view1:AnyObject = subViews[index] as AnyObject
if view1.isKindOfClass(BTNCustom)
{
var check : BTNCustom = subviews.objectAtIndex(ival) as BTNCustom
if (self.tag == check.tag)
{
self.titleLabel.text = "hel";
self.backgroundColor = UIColor.orangeColor()
}
ival++;
}
}

Related

How to set text in GMSAutocompleteViewController in Google Place AutocompleteViewController

I need help to set text in searchBar of GMSAutocompleteViewController when open in my App. I am using GMSAutocompleteViewController in Google Place AutocompleteViewController.
I got a working solution by combining #Youngjin and #Exception's answers for Swift 4 and Google Places 2.6.0
To access the GMSAutocompleteViewController searchbar:
let views = gmsAutoCompleteViewController.view.subviews
let subviewsOfSubview = views.first!.subviews
let subOfNavTransitionView = subviewsOfSubview[1].subviews
let subOfContentView = subOfNavTransitionView[2].subviews
let searchBar = subOfContentView[0] as! UISearchBar
Then to set the text and also search automatically:
searchBar.text = "Your address"
searchBar.delegate?.searchBar?(searchBar, textDidChange: "Your address") // This performs the automatic searching.
I found that I received a EXC_BAD_ACCESS error when attempting to set the text from within
didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController).
So I instead put this code in the completion block where I present the autoCompleteController, which works without issue.
Combining it all together:
let gmsAutoCompleteViewController = GMSAutocompleteViewController()
gmsAutoCompleteViewController.delegate = self
present(gmsAutoCompleteViewController, animated: true) {
let views = gmsAutoCompleteViewController.view.subviews
let subviewsOfSubview = views.first!.subviews
let subOfNavTransitionView = subviewsOfSubview[1].subviews
let subOfContentView = subOfNavTransitionView[2].subviews
let searchBar = subOfContentView[0] as! UISearchBar
searchBar.text = "Your address"
searchBar.delegate?.searchBar?(searchBar, textDidChange: "Your address")
}
EDIT: I found that this solution only seems to work on iOS 11.
let searchBar = subOfContentView[0] as! UISearchBar
will fail on iOS 10, and possibly lower versions.
#Cools
In Swift 3, it seems to have different hierarchy. I've digged into VC and got the place.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
let views = viewController.view.subviews
let subviewsOfSubview = views.first!.subviews
let subOfNavTransitionView = subviewsOfSubview[1].subviews
let subOfContentView = subOfNavTransitionView[2].subviews
let searchBar = subOfContentView[0] as! UISearchBar
searchBar.text = "YOUR_TEXT"
}
However, it has one fault that it does not search automatically. After I'v e got the solution, I will edit again.
Well, I don't necessarily recommend this course of action because if you want more control you should use the UISearchResults Controller. However, here's a swifty-er version:
//Where viewController is the GMSAutocompleteViewController
if let searchBar = (viewController.view.subviews
.flatMap { $0.subviews }
.flatMap { $0.subviews }
.flatMap { $0.subviews }
.filter { $0 == $0 as? UISearchBar}).first as? UISearchBar {
searchBar.text = "YOUR_TEXT_HERE"
searchBar.delegate?.searchBar?(searchBar, textDidChange: "YOUR_TEXT_HERE") // to get the autoComplete Response
}
The benefit of this approach is that you don't have to know exactly where the SearchBar is located. It's basically trying to flatten all the subviews into one array and filter out the SearchBar from the array. If Google decides to change up where the SearchBar is hidden, this approach may still find it. The problem is that you still need to know how many levels of subviews there are. There may be a recursive function that you could set up to deal with this.
I have found out that solutions here are not working for iOS 10. So I have written recursive method for finding concrete view - UISearchBar, in our case:
extension UIView {
func getViewWithConcreteType<T: UIView>()-> T? {
if let concreteSubview = self as? T {
return concreteSubview
}
for subview in subviews {
let concreteView: T? = subview.getViewWithConcreteType()
if concreteView != nil {
return concreteView!
}
}
return nil
}
}
Made some modification to original solution:
let views = autocompleteController.view.subviews
let subviewsOfSubview = views.first!.subviews
let subOfNavTransitionView = subviewsOfSubview[1].subviews
let subOfContentView = subOfNavTransitionView[2].subviews
if let searchBar = subOfContentView[0] as? UISearchBar {
searchBar.text = text
searchBar.delegate?.searchBar?(searchBar, textDidChange: text)
} else {
let searchBar: UISearchBar? = autocompleteController.view.getViewWithConcreteType()
searchBar?.text = text
searchBar.map { $0.delegate?.searchBar?($0, textDidChange: text) }
}
You can inject text in this search bar using something like
func injectTextInSearchView(_ text: String, _ view: UIView) {
if let view = view as? UISearchBar {
view.text = text
view.delegate?.searchBar!(view, textDidChange: text)
return
}
for subview in view.subviews {
injectTextInSearchView(text, subview)
}
}
- (void)viewDidLoad {
[super viewDidLoad];
acController = [[GMSAutocompleteViewController alloc] init];
acController.delegate = self;
}
- (IBAction)btnSearch1:(id)sender {
[self presentViewController:acController animated:YES completion:nil];
}
- (void)didRequestAutocompletePredictions:(GMSAutocompleteViewController *)viewController {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
UIView *Views = viewController.view;
NSArray *Aar2 = [[Views subviews][0] subviews];
UIView *View2 = Aar2[1];
UIView *View3 = [View2 subviews][2];
UISearchBar *Search = [[View3 subviews] firstObject];
Search.text = #"Your text";
[Search.delegate searchBar:Search textDidChange:txtAddress.text];
}
// Cools enjoy man
I'm not very familiar with swift coding but based from the documentation - Customize text and background colors:
You can set the colors of all text and backgrounds in the autocomplete UI control, to make the widget match the visual appearance of your app more closely. There are two ways to set the UI control colors:
By using the native iOS UIAppearance protocol to globally style UI controls where possible. These settings apply to many, but not all, of the UI control elements.
By using the SDK methods on the widget classes to set properties which are not supported by the UIAppearance protocol.
The documentation states Search Bar text color, Search bar tint color and Search bar placeholder text color (default search text) can be styled using (UIAppearance protocol or SDK method).
Hope this info helps.
typedef void (^TTSearchBarHandler)(UISearchBar * searchbar);
__weak UIViewController *weaks = self;
[self searchSubviews:viewController.view completion:^(UISearchBar *searchbar) {
if(searchbar){
[searchbar.delegate searchBar:searchbar textDidChange:#"Your Text"];
}
}];
-(void)searchSubviews:(UIView*)view completion:(TTSearchBarHandler)completion{
if([view isKindOfClass:[UISearchBar class]]){
UISearchBar *searchBar = (UISearchBar*)view;
if(completion){
completion(searchBar);
}
}
else{
for(UIView * subview in view.subviews){
[self searchSubviews:subview completion:^(UISearchBar *searchbar) {
if(completion){
completion(searchbar);
}
}];
}
}
}

NSViewController slow to register mouse events?

I've been working on a small image uploading menubar app for OS X. I've created custom NSView subclass for the uploaded items.
Here's what it looks like by default:
Mouse events are handled by the view's NSViewController in the following way:
import Cocoa
class MenuItemController: NSViewController {
private var trackingArea: NSTrackingArea?
override func mouseEntered(theEvent: NSEvent) {
if let v = self.view as? MenuItemView {
v.shouldHighlight = true
v.needsDisplay = true
}
}
override func mouseExited(theEvent: NSEvent) {
if let v = self.view as? MenuItemView {
v.shouldHighlight = false
v.needsDisplay = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
if (trackingArea == nil) {
trackingArea = NSTrackingArea(rect: self.view.bounds, options: [.ActiveAlways, .MouseEnteredAndExited], owner: self, userInfo: nil)
self.view.addTrackingArea(trackingArea!)
}
/* rest of the code... */
}
}
It works fine until I move my cursor fast over the items. It seems like the mouseExited() event is not called, and the view remains with a blue background (mouse is actually on the Quit button):
I also tried moving the mouse handling into the NSView, but with same results. I appreciate any input! Thanks!
In my opinion, Apple has had bugs in this area.
Assuming you update your tracking area according to apple docs,
adding this additional fix might fix your problem... it fixes it for me in many cases.
I verify in mouseMoved / mouseEntered routine that the mouse cursor is still within my views frame, and if not, call mouseExited: myself.
- (void) adjustTrackingArea
{
if ( trackingArea )
{
[self removeTrackingArea:trackingArea];
[trackingArea release];
}
// determine the tracking options
NSTrackingAreaOptions trackingOptions = // NSTrackingEnabledDuringMouseDrag | // don't track during drag
NSTrackingMouseMoved |
NSTrackingMouseEnteredAndExited |
//NSTrackingActiveInActiveApp | NSTrackingActiveInKeyWindow | NSTrackingActiveWhenFirstResponder |
NSTrackingActiveAlways;
NSRect theRect = [self visibleRect];
trackingArea = [[NSTrackingArea alloc]
initWithRect: theRect
options: trackingOptions
owner: self
userInfo: nil];
[self addTrackingArea:trackingArea];
}
- (void)resetCursorRects
{
[self adjustTrackingArea];
}
- (void)mouseEntered:(NSEvent *)ev
{
[self setNeedsDisplay:YES];
// make sure current mouse cursor location remains under the mouse cursor
NSPoint cursorPt = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:NULL];
// apple bug!!!
//NSPoint cursorPt2 = [self convertPointFromBase:[ev locationInWindow]];
//if ( cursorPt.x != cursorPt2.x )
// NSLog( #"hello old cursorPt" );
NSRect r = [self frame];
if ( cursorPt.x > NSMaxX( r ) || cursorPt.x < 0 )
{
[self mouseExited:ev];
//cursorPt.x = [self convertPointFromBase:[ev locationInWindow]];
//if ( cursorPt.x > NSMaxX( r ) || cursorPt.x < r.origin.x )
return;
}
... your custom stuff here ...
}
- (void)mouseExited:(NSEvent *)theEvent
{
if ( isTrackingCursor == NO )
return;
[[NSCursor arrowCursor] set];
isTrackingCursor = NO;
[self setNeedsDisplay:YES];
}
- (void)mouseMoved:(NSEvent *)theEvent
{
[self mouseEntered:theEvent];
}
I do not understand whether you are installing an NSTrackingArea for the whole window (in this case menu) or for each item. If you are doing the latter, don't do it, or you will spend an endless time to correct the problems you are seeing. The way I handled this buggy behaviour is to create an NSTrackingArea for the whole window and I figure myself where the mouse is and handle the highlighting of each item myself. I know this is not ideal, but this was the only way I was able to solve it after knocking myself in the head for three days.
You can track which was the old active menuitem and set manage it accordingly, something like:
override func mouseEntered(theEvent: NSEvent) {
if let v = self.view as? MenuItemView {
lastEntered.shouldHighLight = false
lastEntered.needsDisplay = true
lastEntered = v;
lastEntered.shouldHighLight = true
lastEntered.needsDisplay = true
}
}
Thus you will ensure that at most one will be active.

"0" is not convertible to BackgroundView in swift

I have this class method and i'm calling this method in my view controller
class func initWithNewFrame(var frame : CGRect?)
{
if let newFrame = frame
{
var backgroundView : BackgroundView = BackgroundView(frame: newFrame)
}
}
I,m calling as
var frameNew : CGRect = CGRectMake(self.view.frame.origin.x,
self.view.frame.origin.y,
self.view.frame.size.width,
self.view.frame.size.height)
self.gradientView = BackgroundView.initWithNewFrame(frameNew)
and i got this error "0" is not convertible to BackgroundView in swift
,
Please solve this and thanks in advance.
Reason:
The error comes from here: self.gradientView = BackgroundView.initWithNewFrame(frameNew)
You assign self.gradientView with return value of initWithNewFrame, but in your code, initWithNewFrame has no return value.
In Swift, no return value means return an empty tuple. So you got "“0” is not convertible to BackgroundView" error, because you want to assign a BackgroundView with an empty tuple.
Maybe this is your expected method:
class func initWithNewFrame(var frame : CGRect?) -> BackgroundView?
{
var backgroundView : BackgroundView? = nil
if let newFrame = frame
{
backgroundView = BackgroundView(frame: newFrame)
}
return backgroundView
}
Update:
if you want to make a custom initializer for class BackgroundView, try this:
convenience init(var frame : CGRect?) {
if let newFrame = frame
{
self.init(newFrame )
}
// Do custom thing
}

Is it possible to change UITabBarItem badge color

I want to change background color of UITabBarItem badge but can't find any resource on how to make it.
UITabBarItem has this available since iOS 10.
var badgeColor: UIColor? { get set }
It's also available via appearence.
if #available(iOS 10, *) {
UITabBarItem.appearance().badgeColor = .green
}
reference docs:
https://developer.apple.com/reference/uikit/uitabbaritem/1648567-badgecolor
Changing the badge-color is now natively supported in iOS 10 and later using the badgeColor property inside your UITabBarItem. See the apple docs for more infos on the property.
Example:
Swift 3: myTab.badgeColor = UIColor.blue
Objective-C: [myTab setBadgeColor:[UIColor blueColor]];
I wrote this piece of code for my app, but I have only tested it in iOS 7.
for (UIView* tabBarButton in self.tabBar.subviews) {
for (UIView* badgeView in tabBarButton.subviews) {
NSString* className = NSStringFromClass([badgeView class]);
// looking for _UIBadgeView
if ([className rangeOfString:#"BadgeView"].location != NSNotFound) {
for (UIView* badgeSubview in badgeView.subviews) {
NSString* className = NSStringFromClass([badgeSubview class]);
// looking for _UIBadgeBackground
if ([className rangeOfString:#"BadgeBackground"].location != NSNotFound) {
#try {
[badgeSubview setValue:[UIImage imageNamed:#"YourCustomImage.png"] forKey:#"image"];
}
#catch (NSException *exception) {}
}
if ([badgeSubview isKindOfClass:[UILabel class]]) {
((UILabel *)badgeSubview).textColor = [UIColor greenColor];
}
}
}
}
}
You're only able to update the badge background with an image, not a color. I have also exposed the badge label if you wanted to update that in some way.
Its important to note that this code must be called after setting the tabBarItem.badgeValue!
EDIT: 4/14/14
The above code will work in iOS 7 when called anywhere. To get it working in iOS 7.1 call it in the view controllers -viewWillLayoutSubviews.
EDIT: 12/22/14
Here's an updated snippet which I'm currently using. I put the code in a category extension for simplicity.
- (void)badgeViews:(void (^)(UIView* badgeView, UILabel* badgeLabel, UIView* badgeBackground))block {
if (block) {
for (UIView* tabBarButton in self.subviews) {
for (UIView* badgeView in tabBarButton.subviews) {
NSString* className = NSStringFromClass([badgeView class]);
if ([className rangeOfString:#"BadgeView"].location != NSNotFound) {
UILabel* badgeLabel;
UIView* badgeBackground;
for (UIView* badgeSubview in badgeView.subviews) {
NSString* className = NSStringFromClass([badgeSubview class]);
if ([badgeSubview isKindOfClass:[UILabel class]]) {
badgeLabel = (UILabel *)badgeSubview;
} else if ([className rangeOfString:#"BadgeBackground"].location != NSNotFound) {
badgeBackground = badgeSubview;
}
}
block(badgeView, badgeLabel, badgeBackground);
}
}
}
}
}
Then when you're ready to call it, it'll look like this.
[self.tabBar badgeViews:^(UIView *badgeView, UILabel *badgeLabel, UIView *badgeBackground) {
}];
EDIT: 11/16/15
It's been brought to my attention that some people need a little more clarity on what's happening in this code. The for loops are searching for a few views which are not publicly accessible. By checking if the views class name contains a part of the expected name, it's ensuring to reach the intended view while not setting off any possible red flags by Apple. Once everything has been located, a block is executed with easy access to these views.
It's noteworthy that the possibility exists for this code to stop working in a future iOS update. For example these internal views could one day acquire different class names. However the chances of that are next to none since even internally Apple rarely refactors classes to this nature. But even if they were to, it would be something along the title of UITabBarBadgeView, which would still reach the expected point in code. Being that iOS9 is well out the door and this code is still working as intended, you can expect this problem to never arise.
I have the same problem and solved it by creating a little category that replace the BadgeView with an UILabel that you can customize easily.
https://github.com/enryold/UITabBarItem-CustomBadge/
For people using Swift, I managed to improve on TimWhiting answer in order to have the badge view working on any screen size and any orientation.
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
for view in self.tabBar.subviews {
if view is CustomTabBadge {
view.removeFromSuperview()
}
}
for index in 0...badgeValues.count-1 {
if badgeValues[index] != 0 {
addBadge(index, value: badgeValues[index], color:UIColor(paletteItem: .Accent), font: UIFont(name: Constants.ThemeApp.regularFontName, size: 11)!)
}
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let badgeView = CustomTabBadge()
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.whiteColor()
badgeView.textAlignment = .Center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = color
badgeView.tag = index
tabBar.addSubview(badgeView)
self.positionBadges()
}
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.positionBadges()
}
// Positioning
func positionBadges() {
var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
return view.userInteractionEnabled // only UITabBarButton are userInteractionEnabled
}
tabbarButtons = tabbarButtons.sort({ $0.frame.origin.x < $1.frame.origin.x })
for view in self.tabBar.subviews {
if view is CustomTabBadge {
let badgeView = view as! CustomTabBadge
self.positionBadge(badgeView, items:tabbarButtons, index: badgeView.tag)
}
}
}
func positionBadge(badgeView: UIView, items: [UIView], index: Int) {
let itemView = items[index]
let center = itemView.center
let xOffset: CGFloat = 12
let yOffset: CGFloat = -14
badgeView.frame.size = CGSizeMake(17, 17)
badgeView.center = CGPointMake(center.x + xOffset, center.y + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
tabBar.bringSubviewToFront(badgeView)
}
}
class CustomTabBadge: UILabel {}
No you can't change the color but you can use your own badges instead. 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.
extension UITabBarController {
func setBadges(badgeValues:[Int]){
var labelExistsForIndex = [Bool]()
for value in badgeValues {
labelExistsForIndex.append(false)
}
for view in self.tabBar.subviews {
if view.isKindOfClass(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 var i=0;i<labelExistsForIndex.count;i++ {
if labelExistsForIndex[i] == false {
if badgeValues[i] > 0 {
addBadge(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){
let itemPosition = CGFloat(index+1)
let itemWidth:CGFloat = tabBar.frame.width / CGFloat(tabBar.items!.count)
let bgColor = color
let xOffset:CGFloat = 12
let yOffset:CGFloat = -9
var badgeView = PGTabBadge()
badgeView.frame.size=CGSizeMake(17, 17)
badgeView.center=CGPointMake((itemWidth * itemPosition)-(itemWidth/2)+xOffset, 20+yOffset)
badgeView.layer.cornerRadius=badgeView.bounds.width/2
badgeView.clipsToBounds=true
badgeView.textColor=UIColor.whiteColor()
badgeView.textAlignment = .Center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = bgColor
badgeView.tag=index
tabBar.addSubview(badgeView)
}
}
class PGTabBadge: UILabel {
}
Swift 3 Here is an updated version of #Kirualex's answer (who improved on #TimWhiting's answer) for Swift 3.
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
for view in self.tabBar.subviews {
if view is CustomTabBadge {
view.removeFromSuperview()
}
}
for index in 0...badgeValues.count-1 {
if badgeValues[index] != 0 {
addBadge(index: index, value: badgeValues[index], color: UIColor.blue, font: UIFont(name: "Helvetica-Light", size: 11)!)
}
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let badgeView = CustomTabBadge()
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = color
badgeView.tag = index
tabBar.addSubview(badgeView)
self.positionBadges()
}
override open func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.positionBadges()
}
// Positioning
func positionBadges() {
var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
return view.isUserInteractionEnabled // only UITabBarButton are userInteractionEnabled
}
tabbarButtons = tabbarButtons.sorted(by: { $0.frame.origin.x < $1.frame.origin.x })
for view in self.tabBar.subviews {
if view is CustomTabBadge {
let badgeView = view as! CustomTabBadge
self.positionBadge(badgeView: badgeView, items:tabbarButtons, index: badgeView.tag)
}
}
}
func positionBadge(badgeView: UIView, items: [UIView], index: Int) {
let itemView = items[index]
let center = itemView.center
let xOffset: CGFloat = 12
let yOffset: CGFloat = -14
badgeView.frame.size = CGSize(width: 17, height: 17)
badgeView.center = CGPoint(x: center.x + xOffset, y: center.y + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
tabBar.bringSubview(toFront: badgeView)
}
}
class CustomTabBadge: UILabel {}
It appears that no. You may only set the value.
From Apple's documentation badge is:
Text that is displayed in the upper-right corner of the item with a
surrounding red oval.
You need to specify tab item at index to change badge color, #available in iOS 10 ,
if #available(iOS 10.0, *)
{
self.kAppTabBarController.tabBar.items![1].badgeColor = YOUR_COLOR
}
You can now do it in the storyboard too, by selecting your tab bar item and going to the attributes inspector.
Since iOS 15 has different approach, what worked in my case:
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
let barAppearance = UITabBarItemAppearance()
barAppearance.normal.badgeBackgroundColor = .green
barAppearance.normal.badgeTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.red]
appearance.stackedLayoutAppearance = barAppearance
tabBar.standardAppearance = appearance
YES, But the only possible solution is to create a custom Tabbar and creating your custom tabbar badge icon. You will find many article/code for creating custom tabbar.
// change TabBar BadgeView background Color
-(void)changeTabBarBadgeViewBgColor:(UITabBar*)tabBar {
for (UIView* tabBarButton in tabBar.subviews) {
for (UIView* badgeView in tabBarButton.subviews) {
NSString* className = NSStringFromClass([badgeView class]);
// looking for _UIBadgeView
if ([className rangeOfString:#"BadgeView"].location != NSNotFound) {
for (UIView* badgeSubview in badgeView.subviews) {
NSString* className = NSStringFromClass([badgeSubview class]);
// looking for _UIBadgeBackground
if ([className rangeOfString:#"BadgeBackground"].location != NSNotFound) {
#try {
[badgeSubview setValue:nil forKey:#"image"];
[badgeSubview setBackgroundColor:[UIColor blueColor]];
badgeSubview.clipsToBounds = YES;
badgeSubview.layer.cornerRadius = badgeSubview.frame.size.height/2;
}
#catch (NSException *exception) {}
}
if ([badgeSubview isKindOfClass:[UILabel class]]) {
((UILabel *)badgeSubview).textColor = [UIColor greenColor];
}
}
}
}
}
}
Hm...it's very easy.
[[self tabBarItem] setBadgeColor:[UIColor greenColor]];
Add below lines of code in UITabBarController :
class RootTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
if #available(iOS 13.0, *) {
let appearance = tabBar.standardAppearance.copy()
setTabBarItemBadgeAppearance(appearance.stackedLayoutAppearance)
setTabBarItemBadgeAppearance(appearance.inlineLayoutAppearance)
setTabBarItemBadgeAppearance(appearance.compactInlineLayoutAppearance)
tabBar.standardAppearance = appearance
if #available(iOS 15.0, *) {
tabBar.scrollEdgeAppearance = appearance
}
}
// Do any additional setup after loading the view.
}
#available(iOS 13.0, *)
private func setTabBarItemBadgeAppearance(_ itemAppearance: UITabBarItemAppearance) {
itemAppearance.normal.badgeBackgroundColor = UIColor.colorBlue207DFF
}
}
Since iOS 15 / Xcode 13, you have to set stackedLayoutAppearance property to change badge color on UITabBarItem. Change just ".blue" with you own color:
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.stackedLayoutAppearance.normal.badgeBackgroundColor = .blue
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance
}
Tested on Xcode 14.1 / iOS 16.
Take a look here # UITabbarItem-CustomBadge.
A complete demonstration is following
it takes only two line of code, if you want to use the default implementation
- (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;
}

Can UITableView Section Headers be Changed when They are 'Floating'?

On iOS devices the section headers in UITableView's have a nice behavior where they stick or 'float' to the top of the screen as you scroll through a section. The section headers in my particular case are loaded from other XIB files.
Is it possible to change the section headers depending on whether or not they are currently floating? Specifically I'd like to add a small shadow to appear under the header only while it's stuck to the top of the view.
Thanks!
Here's the function I created to update whether each header has a shadow or not. All the section headers in this case are a UIView subclass ListHeader. They're retained and returned by the viewForHeaderInSection function.
- (void) updateHeaderShadows {
int i=0;
int sectionHeight = 0;
int totalHeight = 0;
UIView * sectionHeader;
while (i<[self numberOfSectionsInTableView:self.tableView]) {
sectionHeight = [self.tableView rectForSection:i].size.height;
sectionHeader = [self tableView:self.tableView viewForHeaderInSection:i];
if ([sectionHeader respondsToSelector:#selector(shadow)]) {
if (sectionHeader.frame.origin.y == totalHeight || sectionHeader.frame.origin.y == totalHeight + sectionHeight - sectionHeader.frame.size.height) {
[((ListHeader *) sectionHeader).shadow setHidden:YES];
} else {
[((ListHeader *) sectionHeader).shadow setHidden:NO];
}
}
totalHeight += sectionHeight;
i++;
}
}
I haven't tested it yet, but I don't see any reason why it wouldn't be possible.
Just make sure you set the right bounds (because your shadow needs to be on top of your view, not above it).
You can use the following approach:
Use scrollView:didScroll: to get notified about scroll-events.
In this method, check whether you need to add your shadow-view to your (floating) header-view.
If so, add it. (just [view addSubview:shadowView].) Something like CGRectMake(0.f, yourDefaultHeaderHeight, 320.f, yourShadowHeight) should be the frame of your shadowView.
Now, update the bounds of view, so it can show your shadowView: CGRectMake(0.f, 0.f - yourShadowHeight, 320.f, yourDefaultHeaderHeight + 2 * yourShadowHeight).
When you find out that your header isn't floating anymore (by using scrollView:didScroll:), remove the shadow-view.
Your headerViews bounds should be 0.f - yourShadowHeight because if you use just 0.f, it'll blur (I don't know why ...).
You would have to have your own UIView in the header. Then you would need a reference to it. Then hook into scrollViewWillBeginDragging: with your UIScrollViewDelegate. In that function, add the shadow to the custom view.
Hook into scrollViewDidEndDragging:willDecelerate: and remove the shadow in this function.
#Anthony Mattox answer for Swift
protocol SectionHeaderWithShadowProtocol where Self: UIView {
var shadow: Bool { get set }
}
class SectionHeaderView: UITableViewHeaderFooterView, SectionHeaderWithShadowProtocol {
#IBOutlet weak var shadowView: UIView!
var shadow: Bool = false {
didSet {
shadowView.isHidden = shadow
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateHeaderShadows()
}
func updateHeaderShadows() {
var i = 0
var sectionHeight: CGFloat = 0
var totalHeight: CGFloat = 0
while i < numberOfSections() {
sectionHeight = tableView.rect(forSection: i).size.height
if let sectionHeader = tableView.headerView(forSection: i) as? SectionHeaderWithShadowProtocol {
if sectionHeader.frame.origin.y == totalHeight || sectionHeader.frame.origin.y == totalHeight + sectionHeight - sectionHeader.frame.size.height {
sectionHeader.shadow = false
} else {
sectionHeader.shadow = true
}
}
totalHeight += sectionHeight
i += 1
}
}