Custom UIPickerView with three Components each showing label on Selection Indicator - iphone

In my app I am trying to make an custom UIPickerView which contains three components(days, hours and minutes). I have already made the custom picker with three components. And Now I am stuck at how I can add the labels to the selection indicator which shows which component is for days, hours or minutes.
I have already gone through each and every link or question posted on this site but none them helped me.
I am trying to implement something like this image
Can any one suggest me how can I achieve this?

Thats how I achieve this....I have made my Custom PickerView with the help of some code I found...
In .h file:
// LabeledPickerView.h
// LabeledPickerView
#import <UIKit/UIKit.h>
#interface LabeledPickerView : UIPickerView
{
NSMutableDictionary *labels;
}
/** Adds the label for the given component. */
-(void)addLabel:(NSString *)labeltext forComponent:(NSUInteger)component forLongestString:(NSString *)longestString;
#end
and In the .m file...
// LabeledPickerView.m
// LabeledPickerView
#import "LabeledPickerView.h"
#implementation LabeledPickerView
/** loading programmatically */
- (id)initWithFrame:(CGRect)aRect {
if (self = [super initWithFrame:aRect]) {
labels = [[NSMutableDictionary alloc] initWithCapacity:3];
}
return self;
}
/** loading from nib */
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
labels = [[NSMutableDictionary alloc] initWithCapacity:3];
}
return self;
}
- (void) dealloc
{
[labels release];
[super dealloc];
}
#pragma mark Labels
// Add labelText to our array but also add what will be the longest label we will use in updateLabel
// If you do not plan to update label then the longestString should be the same as the labelText
// This way we can initially size our label to the longest width and we get the same effect Apple uses
-(void)addLabel:(NSString *)labeltext forComponent:(NSUInteger)component forLongestString:(NSString *)longestString {
[labels setObject:labeltext forKey:[NSNumber numberWithInt:component]];
NSString *keyName = [NSString stringWithFormat:#"%#_%#", #"longestString", [NSNumber numberWithInt:component]];
if(!longestString) {
longestString = labeltext;
}
[labels setObject:longestString forKey:keyName];
}
//
- (void) updateLabel:(NSString *)labeltext forComponent:(NSUInteger)component {
UILabel *theLabel = (UILabel*)[self viewWithTag:component + 1];
// Update label if it doesn’t match current label
if (![theLabel.text isEqualToString:labeltext]) {
NSString *keyName = [NSString stringWithFormat:#"%#_%#", #"longestString", [NSNumber numberWithInt:component]];
NSString *longestString = [labels objectForKey:keyName];
// Update label array with our new string value
[self addLabel:labeltext forComponent:component forLongestString:longestString];
// change label during fade out/in
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.75];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
theLabel.alpha = 0.00;
theLabel.text = labeltext;
theLabel.alpha = 1.00;
[UIView commitAnimations];
}
}
/**
Adds the labels to the view, below the selection indicator glass-thingy.
The labels are aligned to the right side of the wheel.
The delegate is responsible for providing enough width for both the value and the label.
*/
- (void)didMoveToWindow {
// exit if view is removed from the window or there are no labels.
if (!self.window || [labels count] == 0)
return;
UIFont *labelfont = [UIFont boldSystemFontOfSize:15];
// find the width of all the wheels combined
CGFloat widthofwheels = 0;
for (int i=0; i<self.numberOfComponents; i++) {
widthofwheels += [self rowSizeForComponent:i].width;
}
// find the left side of the first wheel.
// seems like a misnomer, but that will soon be corrected.
CGFloat rightsideofwheel = (self.frame.size.width - widthofwheels) / 2;
// cycle through all wheels
for (int component=0; component<self.numberOfComponents; component++) {
// find the right side of the wheel
rightsideofwheel += [self rowSizeForComponent:component].width;
// get the text for the label.
// move on to the next if there is no label for this wheel.
NSString *text = [labels objectForKey:[NSNumber numberWithInt:component]];
if (text) {
// set up the frame for the label using our longestString length
NSString *keyName = [NSString stringWithFormat:#"%#_%#", [NSString stringWithString:#"longestString"], [NSNumber numberWithInt:component]];
NSString *longestString = [labels objectForKey:keyName];
CGRect frame;
frame.size = [longestString sizeWithFont:labelfont];
// center it vertically
frame.origin.y = (self.frame.size.height / 2) - (frame.size.height / 2) - 0.5;
// align it to the right side of the wheel, with a margin.
// use a smaller margin for the rightmost wheel.
frame.origin.x = rightsideofwheel - frame.size.width -
(component == self.numberOfComponents - 1 ? 5 : 7);
// set up the label. If label already exists, just get a reference to it
BOOL addlabelView = NO;
UILabel *label = (UILabel*)[self viewWithTag:component + 1];
if(!label) {
label = [[[UILabel alloc] initWithFrame:frame] autorelease];
addlabelView = YES;
}
label.text = text;
label.font = labelfont;
label.backgroundColor = [UIColor clearColor];
label.shadowColor = [UIColor whiteColor];
label.shadowOffset = CGSizeMake(0,1);
// Tag cannot be 0 so just increment component number to esnure we get a positive
// NB update/remove Label methods are aware of this incrementation!
label.tag = component + 1;
if(addlabelView) {
/*
and now for the tricky bit: adding the label to the view.
kind of a hack to be honest, might stop working if Apple decides to
change the inner workings of the UIPickerView.
*/
if (self.showsSelectionIndicator) {
// if this is the last wheel, add label as the third view from the top
if (component==self.numberOfComponents-1)
[self insertSubview:label atIndex:[self.subviews count]-3];
// otherwise add label as the 5th, 10th, 15th etc view from the top
else
[self insertSubview:label aboveSubview:[self.subviews objectAtIndex:5*(component+1)]];
} else
// there is no selection indicator, so just add it to the top
[self addSubview:label];
}
if ([self.delegate respondsToSelector:#selector(pickerView:didSelectRow:inComponent:)])
[self.delegate pickerView:self didSelectRow:[self selectedRowInComponent:component] inComponent:component];
}
}
}
And call this addLabel: method with the label text and component tag and thats it..!!

Download the Source code Of custom UIPickerView Control .
Custom UiPickerView.
Hope it Helps to You :)

Related

How to add views between UICollectionViewCells in a UICollectionView?

I'm trying to add UIViews between my UICollectionViewCells in my UICollectionView and I don't know how I could do that. I'm trying to accomplish something like this:
I'll probably need to write a custom UICollectionViewLayout, but I don't really know where to start.
I studied more of how UICollectionViewLayouts work and figured out how to solve it. I have an UICollectionReusableView subclass called OrangeView that will be positioned between my views, than I wrote an UICollectionViewFlowLayout subclass called CategoriesLayout that will deal with my layout.
Sorry for the big block of code, but here is how it looks like:
#implementation CategoriesLayout
- (void)prepareLayout {
// Registers my decoration views.
[self registerClass:[OrangeView class] forDecorationViewOfKind:#"Vertical"];
[self registerClass:[OrangeView class] forDecorationViewOfKind:#"Horizontal"];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath {
// Prepare some variables.
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:indexPath.row+1 inSection:indexPath.section];
UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *nextCellAttributes = [self layoutAttributesForItemAtIndexPath:nextIndexPath];
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];
CGRect baseFrame = cellAttributes.frame;
CGRect nextFrame = nextCellAttributes.frame;
CGFloat strokeWidth = 4;
CGFloat spaceToNextItem = 0;
if (nextFrame.origin.y == baseFrame.origin.y)
spaceToNextItem = (nextFrame.origin.x - baseFrame.origin.x - baseFrame.size.width);
if ([decorationViewKind isEqualToString:#"Vertical"]) {
CGFloat padding = 10;
// Positions the vertical line for this item.
CGFloat x = baseFrame.origin.x + baseFrame.size.width + (spaceToNextItem - strokeWidth)/2;
layoutAttributes.frame = CGRectMake(x,
baseFrame.origin.y + padding,
strokeWidth,
baseFrame.size.height - padding*2);
} else {
// Positions the horizontal line for this item.
layoutAttributes.frame = CGRectMake(baseFrame.origin.x,
baseFrame.origin.y + baseFrame.size.height,
baseFrame.size.width + spaceToNextItem,
strokeWidth);
}
layoutAttributes.zIndex = -1;
return layoutAttributes;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *baseLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray * layoutAttributes = [baseLayoutAttributes mutableCopy];
for (UICollectionViewLayoutAttributes *thisLayoutItem in baseLayoutAttributes) {
if (thisLayoutItem.representedElementCategory == UICollectionElementCategoryCell) {
// Adds vertical lines when the item isn't the last in a section or in line.
if (!([self indexPathLastInSection:thisLayoutItem.indexPath] ||
[self indexPathLastInLine:thisLayoutItem.indexPath])) {
UICollectionViewLayoutAttributes *newLayoutItem = [self layoutAttributesForDecorationViewOfKind:#"Vertical" atIndexPath:thisLayoutItem.indexPath];
[layoutAttributes addObject:newLayoutItem];
}
// Adds horizontal lines when the item isn't in the last line.
if (![self indexPathInLastLine:thisLayoutItem.indexPath]) {
UICollectionViewLayoutAttributes *newHorizontalLayoutItem = [self layoutAttributesForDecorationViewOfKind:#"Horizontal" atIndexPath:thisLayoutItem.indexPath];
[layoutAttributes addObject:newHorizontalLayoutItem];
}
}
}
return layoutAttributes;
}
#end
I also wrote a category with some methods to check if an index path is the last in a line, in the last line or the last in a section:
#implementation UICollectionViewFlowLayout (Helpers)
- (BOOL)indexPathLastInSection:(NSIndexPath *)indexPath {
NSInteger lastItem = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:indexPath.section] -1;
return lastItem == indexPath.row;
}
- (BOOL)indexPathInLastLine:(NSIndexPath *)indexPath {
NSInteger lastItemRow = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:indexPath.section] -1;
NSIndexPath *lastItem = [NSIndexPath indexPathForItem:lastItemRow inSection:indexPath.section];
UICollectionViewLayoutAttributes *lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItem];
UICollectionViewLayoutAttributes *thisItemAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
return lastItemAttributes.frame.origin.y == thisItemAttributes.frame.origin.y;
}
- (BOOL)indexPathLastInLine:(NSIndexPath *)indexPath {
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:indexPath.row+1 inSection:indexPath.section];
UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *nextCellAttributes = [self layoutAttributesForItemAtIndexPath:nextIndexPath];
return !(cellAttributes.frame.origin.y == nextCellAttributes.frame.origin.y);
}
#end
And this is the final result:
Looks like if your collectionView background was green and contentView white you could get the horizontals with a space between the cells minimumLineSpacing. The vertical gap would be the tricky part, but if you were creative with your contentView and set the minimumInteritemSpacing carefully you could get it.
If you're using sections and the layout is appropriate for it, you might use section headers and footers.
But based on your illustration, it looks like you just need to define the UICollectionViewCell to contain those views. So, where you register your class:
[collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
put those border images in the UICollectionView cell subclass (in the above case, "CollectionViewCell"). That seems like the easiest approach.
Here's one I use:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.restorationIdentifier = #"Cell";
self.backgroundColor = [UIColor clearColor];
self.autoresizingMask = UIViewAutoresizingNone;
const CGFloat borderWidth = 3.0f;
UIView *bgView = [[UIView alloc] initWithFrame:frame];
bgView.layer.borderColor = [UIColor blackColor].CGColor;
bgView.layer.borderWidth = borderWidth;
bgView.layer.cornerRadius = 6.0f;
self.selectedBackgroundView = bgView;
}
return self;
}
Wow. That's a lot of code in the other answers just for a separator line between rows..
This is how I solved it. First you'll need to add the line separator inside the cell. Make sure you keep dragging it making it wider than the actual cell width so if your cell width is 60p your separator line will be 70.
#implementation CollectionViewController
{
NSArray *test;
int currentLocInRow;
}
inside cellForItemAtIndexPath:
if((indexPath.row+1) % 4 == 0)
{
cell.clipsToBounds = YES;
}
else
{
cell.clipsToBounds = NO;
}
currentLocInRow++;
if([test count] - indexPath.row+1 < 4 - currentLocInRow)
{
cell.lineSeparator.alpha = 0;
}
else
{
cell.lineSeparator.alpha = 1;
}
if(currentLocInRow==4)currentLocInRow=0;
If you want a separator at the end of the collection view but there's a chance that you won't get 4 cells at the last row you can add a simple Collection Reusable View as Footer.

Trouble in making hidden button Visible

Following code i am writing to hide some buttons in viewDidLoad. Here Buttons Are hiding
- (void)viewDidLoad
{
for (int i = 100; i<117; i++)
{
UIButton *smileyButton = (UIButton *)[scroll viewWithTag:i];
UITextField *smileyFields = (UITextField *)[scroll viewWithTag:i];
UIImageView *smileyImage = (UIImageView *)[scroll viewWithTag:i];
smileyFields.hidden = YES;
smileyButton.hidden = YES;
}
}
Now in Following Action am making Buttons Visible. But buttons are not Visible
-(IBAction)editButton:(id)sender
{
for (int i = 100; i<117; i++)
{
UIButton *smileyButton = (UIButton *)[scroll viewWithTag:i];
UITextField *smileyFields = (UITextField *)[scroll viewWithTag:i];
UIImageView *smileyImage = (UIImageView *)[scroll viewWithTag:i];
[smileyFields setHidden:NO]; //TextFields Not Visible
[smileyButton setHidden:NO]; //Buttons Not Visbile
}
}
If you have several views with the same tag, function viewWithTag will return only one view, so if you call this 3 times, you get always the same view.
To do what you want, you could iterate all subviews and check tags:
for (UIView *aView in scrollView.subviews) {
if (aView.tag >= 100 && aView.tag < 117) {
aView.hidden = NO;
}
}
Are you building the view controller in Interface Builder? If so, set the Tag of each thing you want to hide to a different number: try something simple like 1, 2, 3, etc. If you're building in code set the tag property instead. Remember your maximum tag number (let's assume it's 4).
Then add the following to your .h:
- (void)setTaggedViewsHidden:(BOOL)hidden;
and the following to your .m:
- (void)setTaggedViewsHidden:(BOOL)hidden {
for (NSInteger tag = 1; tag <= 4; tag++) {
[scroll viewWithTag:tag].hidden = hidden;
}
}
In your viewDidLoad call it like so:
[self setTaggedViewsHidden:YES];
and in your editButton: selector call it as:
[self setTaggedViewsHidden:NO];
Remember to adjust the code in setTaggedViewsHidden to match the tags you're using. The best way to do this is to #define a constant for the min and max tags and use those in the for loop.

How to have a Score Integer updated and displayed in Cocos2d?

I am obviously making a game that has a score. How do I call an update method and have the integer actually displayed in the Top-Right corner?
Here, this might work
In the .h file:
#interface HelloWorld : CCLayer {
int score;
CCLabelTTF *scoreLabel;
}
- (void)addPoint;
In the .m file:
In the init method:
//Set the score to zero.
score = 0;
//Create and add the score label as a child.
scoreLabel = [CCLabelTTF labelWithString:#"8" fontName:#"Marker Felt" fontSize:24];
scoreLabel.position = ccp(240, 160); //Middle of the screen...
[self addChild:scoreLabel z:1];
Somewhere else:
- (void)addPoint
{
score = score + 1; //I think: score++; will also work.
[scoreLabel setString:[NSString stringWithFormat:#"%#", score]];
}
Now just call: [self addPoint]; whenever the user kills an enemy.
That should work, tell me if it didn't because I have not tested it.
in header file:
#interface GameLayer : CCLayer
{
CCLabelTTF *_scoreLabel;
}
-(void) updateScore:(int) newScore;
in implementation file:
-(id) init
{
if( (self=[super init])) {
// ..
// add score label
_scoreLabel = [CCLabelTTF labelWithString:#"0" dimensions:CGSizeMake(200,30) alignment:UITextAlignmentRight fontName:#"Marker Felt" fontSize:30];
[self addChild:_scoreLabel];
_scoreLabel.position = ccp( screenSize.width-100, screenSize.height-20);
}
return self;
}
-(void) updateScore:(int) newScore {
[_scoreLabel setString: [NSString stringWithFormat:#"%d", newScore]];
}
EDIT: if you don't want to use an ivar, you can use tags:
[self addChild:scoreLabel z:0 tag:kScoreLabel];
// ...
CCLabelTTF *scoreLabel = (CCLabelTTF*)[self getChildByTag:kScoreLabel];
EDIT 2: For performance reasons you should switch to CCLabelAtlas or CCBitmapFontAtlas if you update the score very frequently.
Also read the cocos2d programming guide about labels.
Using UILabel
UILabel.text = [NSString stringWithFormat:#"%lu",score];
Move the UILabel in the top of the view using interface builder
you could also create it programmatically
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,500,30)];
[[self view] addSubview:label];
[label release]; // dont leak :)

UISegmentedControl selected segment color

Is there any way to customize color of selected segment in UISegmentedControl?
I've found segmentedController.tintColor property, which lets me customize color of the whole segmented control.
The problem is, when I select bright color for tintColor property, selected segment becomes almost unrecognizable (its color is almost the same as the rest of segmented control, so its hard to distinguish selected and unselected segments). So I cannot use any good bright colors for segmented control.
The solution would be some separate property for selected segment color but I cannot find it. Did anyone solve this?
Here is the absolute simplest way to change the selected segment to any RGB color. No subclassing or hacks required.
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
UIColor *newTintColor = [UIColor colorWithRed: 251/255.0 green:175/255.0 blue:93/255.0 alpha:1.0];
segmentedControl.tintColor = newTintColor;
UIColor *newSelectedTintColor = [UIColor colorWithRed: 0/255.0 green:175/255.0 blue:0/255.0 alpha:1.0];
[[[segmentedControl subviews] objectAtIndex:0] setTintColor:newSelectedTintColor];
This example shows the important steps:
Sets the control style to
"StyleBar", which is required for it
to work
Sets the un-selected color for the
entire control first to orange
Sets the color of the selected
segment to green
Notes:
Steps 1 and 2 can be done in
interface builder, or in code as
shown. However step 3 can only be done
in code
The color values being set with
notation like this "123.0/255.0" is
just a way to make the RGB values
stand out instead the normalized
float values required by UIColor
(just ignore it if you like)
I found A Simple Way to Add Color for Selected Segment in UISegmentcontrol
sender is UISegmentControl
for (int i=0; i<[sender.subviews count]; i++)
{
if ([[sender.subviews objectAtIndex:i]isSelected] )
{
UIColor *tintcolor=[UIColor colorWithRed:127.0/255.0 green:161.0/255.0 blue:183.0/255.0 alpha:1.0];
[[sender.subviews objectAtIndex:i] setTintColor:tintcolor];
}
else
{
[[sender.subviews objectAtIndex:i] setTintColor:nil];
}
}
Check its Working For Me
To do this you simply have to find the selected segment, for example by iterating over the segmented control's subviews and testing the isSelected property, then simply call the setTintColor: method on that subview.
I did this by connecting an action to each segmented control on the ValueChanged event in Interface Builder, I connected them to this this method in the view controller file which is essentially msprague's answer:
- (IBAction)segmentedControlValueChanged:(UISegmentedControl*)sender
{
for (int i=0; i<[sender.subviews count]; i++)
{
if ([[sender.subviews objectAtIndex:i] respondsToSelector:#selector(isSelected)] && [[sender.subviews objectAtIndex:i]isSelected])
{
[[sender.subviews objectAtIndex:i] setTintColor:[UIColor whiteColor]];
}
if ([[sender.subviews objectAtIndex:i] respondsToSelector:#selector(isSelected)] && ![[sender.subviews objectAtIndex:i] isSelected])
{
[[sender.subviews objectAtIndex:i] setTintColor:[UIColor blackColor]];
}
}
}
To ensure that the control is displayed correctly each time the view is opened by the user I also had to override the -(void)viewDidAppear:animated method and call the method as follows:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Ensure the segmented controls are properly highlighted
[self segmentedControlValueChanged:segmentedControlOne];
[self segmentedControlValueChanged:segmentedControlTwo];
}
For some bonus points if you do want to set the segmented control to use a white tint color on selection then you will also want to change the color of the text to black when it's selected, you can do this like so:
//Create a dictionary to hold the new text attributes
NSMutableDictionary * textAttributes = [[NSMutableDictionary alloc] init];
//Add an entry to set the text to black
[textAttributes setObject:[UIColor blackColor] forKey:UITextAttributeTextColor];
//Set the attributes on the desired control but only for the selected state
[segmentedControlOne setTitleTextAttributes:textAttributes forState:UIControlStateSelected];
With the introduction of iOS 6 setting the tint color of the selected item for the first time in the viewDidAppear method wont work, to get around this I used grand central dispatch to change the selected color after a fraction of a second like so:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self segmentedControlValueChanged:segmentedControlOne];
});
For some reason Apple dont allow you to change the color of standard UISegmentedControls.
There is however a "legal" way around it which is to change the segmented control style to UISegmentedControlStyleBar. This makes it look slightly different which you may not like but it does allow color.
NSArray *itemArray = [NSArray arrayWithObjects: #"One", #"Two", #"Three", nil];
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:itemArray];
//Change Bar Style and ad to view then release segmented controller
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.tintColor = [UIColor colorWithRed:.9 green:.1 blue:.1 alpha:1];
[self.view addSubview:segmentedControl];
[segmentedControl release];
Hope this helped,
Seb Kade
"I'm here to help"
Edit: This solution doesn't work on iOS 6. See David Thompson's answer below.
This thread is really old, but none of the simple answers worked properly for me.
The accepted answer works as long as you revert the color of the deselected segmented controls.
Something like this will work in your value changed function:
for (int i=0; i<[control.subviews count]; i++)
{
if ([[control.subviews objectAtIndex:i]isSelected] )
{
UIColor *tintcolor=[UIColor colorWithRed:127.0/255.0 green:161.0/255.0 blue:183.0/255.0 alpha:1.0];
[[control.subviews objectAtIndex:i] setTintColor:tintcolor];
} else {
UIColor *tintcolor=[UIColor grayColor]; // default color
[[control.subviews objectAtIndex:i] setTintColor:tintcolor];
}
}
I know this is an old question But now in xcode 11 +, you can set selected segment Tint colour
In code us can use selectedSegmentTintColor. available iOS 13+
Here is my modified version of uihacker's CustomSegmentedControl (see credit in comment). The idea is I change the way to find the subview that should have the tintColor changed, from using selectedIndex to isSelected method. Because I was working with a custom UISegmentedControl that has 3 or more segments which the subview ordering changes randomly (even uihacker's "hasSetSelectedIndexOnce" flag doesn't fix this!). The code is still in early dev stage so use it at your own risk. Any comment is welcomed :)
Also, I added support to interface builder, and override setSelectedSegmentIndex so that it also updates the color. Enjoy!
CustomSegmentedControl.h
//
// CustomSegmentedControl.h
//
// Created by Hlung on 11/22/54 BE.
// Copyright (c) 2554 __MyCompanyName__. All rights reserved.
//
// Credit: http://uihacker.blogspot.com/2010/05/iphone-uisegmentedcontrol-custom-colors.html
#interface CustomSegmentedControl : UISegmentedControl {
UIColor *offColor,*onColor;
}
#property (nonatomic,retain) UIColor *offColor,*onColor;
-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor;
#end
CustomSegmentedControl.m
#import "CustomSegmentedControl.h"
#interface CustomSegmentedControl (private)
-(void)setInitialMode;
-(void)toggleHighlightColors;
#end
#implementation CustomSegmentedControl
#synthesize offColor,onColor;
-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor {
if (self = [super initWithItems:items]) {
// Initialization code
self.offColor = offcolor;
self.onColor = oncolor;
[self setInitialMode];
// default to 0, other values cause arbitrary highlighting bug
[self setSelectedSegmentIndex:0];
}
return self;
}
- (void)awakeFromNib {
// default colors
self.offColor = [UIColor colorWithWhite:0.8 alpha:1];
self.onColor = self.tintColor;
[self setInitialMode];
[self setSelectedSegmentIndex:0];
}
-(void)setInitialMode
{
// set essential properties
[self setBackgroundColor:[UIColor clearColor]];
[self setSegmentedControlStyle:UISegmentedControlStyleBar];
// loop through children and set initial tint
for( int i = 0; i < [self.subviews count]; i++ )
{
[[self.subviews objectAtIndex:i] setTintColor:nil];
[[self.subviews objectAtIndex:i] setTintColor:offColor];
}
// listen for updates, [self setSelectedSegmentIndex:0] triggers UIControlEventValueChanged in 5.0, 4.3 doesn't (facepalm), use if( self.window ) to fix this
[self addTarget:self action:#selector(toggleHighlightColors) forControlEvents:UIControlEventValueChanged];
}
// ---------------
// hlung's version
// ---------------
-(void)toggleHighlightColors
{
// the subviews array order randomly changes all the time, change to check for "isSelected" instead
for (id v in self.subviews) {
if ([v isSelected]) [v setTintColor:onColor];
else [v setTintColor:offColor];
}
}
// override: update color when set selection
- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex {
[super setSelectedSegmentIndex:selectedSegmentIndex];
[self toggleHighlightColors];
}
// ---------------
#end
Use this:
[[UISegmentedControl appearance] setTitleTextAttributes:#{NSForegroundColorAttributeName : [UIColor colorWithRed:255.0/255 green:37.0/255 blue:99.0/255 alpha:1.0]} forState:UIControlStateSelected];
Not sure if this will get approved by the app store, but I wrote a subclass to UISegmentedControl that lets you set a custom selected and unselected color. Check the notes for more info:
http://uihacker.blogspot.com/2010/05/iphone-uisegmentedcontrol-custom-colors.html
To clarify the answer provided above by #jothikenpachi we found the following UISegmentController category worked well in iOS6 and allows for an arbitrary on/off color scheme on segments. Plus it will fail gracefully if the private methods isSelected/setTintColor: are changed in future OS releases. Caveats around private API calls, etc.
#implementation UISegmentedControl(CustomTintExtension) {
-(void) updateCustomTintColorOn:(UIColor*)onColor Off:(UIColor*)offColor {
// Convenience function to rest the tint colors after selection, called upon change of selected index
SEL tint = #selector(setTintColor:);
for (UIView *view in [self subviews]) {
// Loop through the views...
if (view && ([view respondsToSelector:tint])) {
[view performSelector:tint withObject:nil];
}
if (view && ([view respondsToSelector:tint])) {
[view performSelector:tint withObject:offColor];
}
}
// Checking if segment subview is selected...
SEL isSelected = #selector(isSelected);
for (UIView *view in [self subviews]) {
if ([view respondsToSelector:isSelected] && [view performSelector:isSelected withObject:nil])
{
[view performSelector:tint withObject:onColor];
break;
}
}
}
Note, this category method would be called from within the UISegmentController's - (IBAction) segmentAction: (id)sender method.
Also note that with iOS6 it seems you may need to call this method initially in the governing UIViewController's - (void)viewDidAppear:(BOOL)animated which may result in a animation flash. To minimize this, try setting the "offColor" as the UISegmentController's tintColor in IB.
I just ran into this issue on iOS 7, which works differently than iOS6.
In iOS 7, the color of the label for the selected segment is the same color as the UISegementControl background. The only way to change it on iOS 7 is to set the background color of the UISegmentControl.
segmentControl.backgroundColor = customColor;
I used this and it changed all the colors in one step.
mySegmentedControl.tintColor = [UIColor redColor]
I found I could use tag on the subviews with the same index as the segments, so that in any order they the segments will be colored correctly.
// In viewWillAppear set up the segmented control
// then for 3 segments:
self.navigationItem.titleView = segmentedControl;
//Order of subviews can change randomly!, so Tag them with same index as segment
[[[segmentedControl subviews]objectAtIndex:0]setTag:0];
[[[segmentedControl subviews]objectAtIndex:1]setTag:1];
[[[segmentedControl subviews]objectAtIndex:2]setTag:2];
// color follows the selected segment
- (IBAction)mySelector:(id)sender {
selector = [sender selectedSegmentIndex]
for (id seg in [segmentedControl subviews]) {
for (id label in [seg subviews]) {
if ([seg tag] == selector){
[seg setTintColor:selectedColor];
} else {
[seg setTintColor:nonSelectedColor];
}
}
}
}
// in viewDidAppear for returning to the view
[segmentedControl setSelectedSegmentIndex:selector];
for (id seg in [segmentedControl subviews]) {
for (id label in [seg subviews]) {
if ([seg tag] == selector){
[seg setTintColor:selectedColor];
} else {
[seg setTintColor:nonSelectedColor];
}
}
}
The top two solutions didn't work for me when switching between segments.
My solution was to handle the segment change event in my view controller and then call this method each time the segment is changed:
+ (void)setSegmentedControl:(UISegmentedControl *)segmentedControl
selectedColor:(UIColor *)selectedColor
deselectedColor:(UIColor *)deselectedColor
{
for (int i = 0; i < segmentedControl.subviews.count; i++)
{
id subView = [segmentedControl.subviews objectAtIndex:i];
if ([subView isSelected])
[subView setTintColor:selectedColor];
else
[subView setTintColor:deselectedColor];
}
}
I am wondering why anyone have not mentioned about UIAppearanceProxy
Apple Doc::
https://developer.apple.com/documentation/uikit/uisegmentedcontrol#1653545
Sample Code:
private class func applyUISegmentControlAppearance(){
let apperance = UISegmentedControl.appearance()
// Set Navigation bar Title colour
let unselAttrib = [NSForegroundColorAttributeName:UIColor.yellow,
NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
let selAttrib = [NSForegroundColorAttributeName:UIColor.red,
NSFontAttributeName: UIFont.boldSystemFont(ofSize: 15)]
apperance.setTitleTextAttributes(unselAttrib, for: .normal)
apperance.setTitleTextAttributes(selAttrib, for: .selected)
}
Call From:
You can call this method in AppDelegate from
application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
For doing your kind of thing, one might have to access the undocumented features and hacks, which will certainly make apple furious, and that may lead to the rejection of your application.
Now, the solution lies in other trick that you use two buttons instead and have their images interchanged when they are clicked. Keep the buttons closer and images of half segmented control to give the illusion of segmented control and that is all I can suggest you.
Hope this helps.
Thanks,
Madhup
You can tag each of the segments, then set the TintColor forTag:
#define kTagOffState 0
#define kTagOnState 2
#define UIColorFromRGB(rgbValue) [UIColor \
colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
//usage UIColor color = UIColorFromRGB(0xF7F7F7);
UIColor onColor = UIColorFromRGB(0xF7F7F7);
UIColor offColor = UIColorFromRGB(0x878787);
[multiStateControl setTag:kTagOffState forSegmentAtIndex:0];
[multiStateControl setTag:kTagOnState forSegmentAtIndex:1];
[multiStateControl setTintColor:onColor forTag:kTagOnState];
[multiStateControl setTintColor:offColor forTag:kTagOffState];
I found the answers above very helpful. I am using the segmented control to set the precision of a knob. I took a hybrid of the answers above and came up with this:
-(void) viewDidLoad {
NSArray *segments = [NSArray arrayWithObjects:#"Course", #"Fine",nil];
[knob setPrecision:0.1]; // initial precision
// Set starting values
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segments];
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.frame = CGRectMake(120, 680, 228, 30);
[segmentedControl addTarget:self action:#selector(precisionSelect:) forControlEvents:UIControlEventValueChanged];
segmentedControl.momentary = YES;
[self.view addSubview:segmentedControl];
}
- (void)precisionSelect:(UISegmentedControl*)sender
{
UIColor *tintcolor = [UIColor darkGrayColor];
if (sender.selectedSegmentIndex == 0) {
[[sender.subviews objectAtIndex:0] setTintColor:nil];
[[sender.subviews objectAtIndex:1] setTintColor:tintcolor];
[knob setPrecision:0.1]; // Coarse
} else {
[[sender.subviews objectAtIndex:0] setTintColor:tintcolor];
[[sender.subviews objectAtIndex:1] setTintColor:nil];
[knob setPrecision:0.05]; // Fine
}
}
Hope this helps others..
A key for me, was being able to reset the unselected index using: setTintColor:nil];
- (IBAction)segmentControlValueChanged:(UISegmentedControl *)sender
{
if ([[sender.subviews firstObject] respondsToSelector:#selector(setTintColor:)]) {
for (id segment in sender.subviews) {
if ([segment respondsToSelector:#selector(isSelected)] && [segment isSelected]) {
[segment setTintColor:[UIColor redColor]];
} else {
[segment setTintColor:[UIColor grayColor]];
}
}
}
}
Try this solution.
#IBAction func dashBoardSegmentValueChanged(sender: AnyObject) {
switch dashBoardSegment.selectedSegmentIndex
{
case 0:
sender.subviews.last?.backgroundColor = UIColor.whiteColor()
sender.subviews.first?.backgroundColor = UIColor.clearColor()
break;
case 1:
sender.subviews.first?.backgroundColor = UIColor.whiteColor()
sender.subviews.last?.backgroundColor = UIColor.clearColor()
break;
default:
break;
}
}
Note: Make sure you select one segment subview as initial selected for easiness. It works if you have two segment subviews.
- (IBAction)segmentedControlValueChanged:(UISegmentedControl *)sender {
for (int i = 0; i < sender.subviews.count; i++) {
UIControl *component = [sender.subviews objectAtIndex:i];
if ([component respondsToSelector:#selector(isSelected)]) {
UIColor *selectedColor = [UIColor greenColor];
UIColor *normalColor = [UIColor blackColor];
UIColor *tint = component.isSelected ? selectedColor : normalColor;
[component setTintColor:tint];
}
}
}
[segmentedControl setSelectedSegmentTintColor:[UIColor darkGrayColor]];
//For iOS 13
This Swift 4 code works for me
segmentedControl.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.red], for: .selected)

Multi-line TextField (similar to SMS) and / or 'Done' button in UITextView

I've been researching this for a few days now, and would appreciate a little help. Is there any way to generate a multi-line UITextField like Apple use in the SMS application? The useful thing about this control is that it has the 'sunk' appearance that makes it clear that it is a text entry box, but at the same time, it expands on each new-line character.
Failing that, if I'm forced to use a UITextView, can anyone advise how best to dismiss the keyboard ? Both the 'Done' and the 'Go' buttons just appear to generate newline characters ('\n'). This seems wrong to me - surely at least one of these should generate a different character, so that I can still allow for newline characters, but also dismiss my keyboard on a specific key press.
Am I missing something simple here ?
Thanks in advance :)
Maybe you can build upon a class I wrote? It's the same as tttexteditor, without the ugly glitches: http://www.hanspinckaers.com/multi-line-uitextview-similar-to-sms
An old question, but after several hours I've figured out how to make it the same perfectly as in Instagram (it has the best algorithm among all BTW)
Initialize with this:
// Input
_inputBackgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, size.height - _InputBarHeight, size.width, _InputBarHeight)];
_inputBackgroundView.autoresizingMask = UIViewAutoresizingNone;
_inputBackgroundView.contentMode = UIViewContentModeScaleToFill;
_inputBackgroundView.userInteractionEnabled = YES;
[self addSubview:_inputBackgroundView];
[_inputBackgroundView release];
[_inputBackgroundView setImage:[[UIImage imageNamed:#"Footer_BG.png"] stretchableImageWithLeftCapWidth:80 topCapHeight:25]];
// Text field
_textField = [[UITextView alloc] initWithFrame:CGRectMake(70.0f, 0, 185, 0)];
_textField.backgroundColor = [UIColor clearColor];
_textField.delegate = self;
_textField.contentInset = UIEdgeInsetsMake(-4, -2, -4, 0);
_textField.showsVerticalScrollIndicator = NO;
_textField.showsHorizontalScrollIndicator = NO;
_textField.font = [UIFont systemFontOfSize:15.0f];
[_inputBackgroundView addSubview:_textField];
[_textField release];
[self adjustTextInputHeightForText:#""];
Fill UITextView delegate methods:
- (void) textViewDidBeginEditing:(UITextView*)textView {
[self adjustTextInputHeightForText:_textField.text];
}
- (void) textViewDidEndEditing:(UITextView*)textView {
[self adjustTextInputHeightForText:_textField.text];
}
- (BOOL) textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
if ([text isEqualToString:#"\n"])
{
[self performSelector:#selector(inputComplete:) withObject:nil afterDelay:.1];
return NO;
}
else if (text.length > 0)
{
[self adjustTextInputHeightForText:[NSString stringWithFormat:#"%#%#", _textField.text, text]];
}
return YES;
}
- (void) textViewDidChange:(UITextView*)textView {
[self adjustTextInputHeightForText:_textField.text];
}
And the trick is...
- (void) adjustTextInputHeightForText:(NSString*)text {
int h1 = [text sizeWithFont:_textField.font].height;
int h2 = [text sizeWithFont:_textField.font constrainedToSize:CGSizeMake(_textField.frame.size.width - 16, 170.0f) lineBreakMode:UILineBreakModeWordWrap].height;
[UIView animateWithDuration:.1f animations:^
{
if (h2 == h1)
{
_inputBackgroundView.frame = CGRectMake(0.0f, self.frame.size.height - _InputBarHeight, self.frame.size.width, _InputBarHeight);
}
else
{
CGSize size = CGSizeMake(_textField.frame.size.width, h2 + 24);
_inputBackgroundView.frame = CGRectMake(0.0f, self.frame.size.height - size.height, self.frame.size.width, size.height);
}
CGRect r = _textField.frame;
r.origin.y = 12;
r.size.height = _inputBackgroundView.frame.size.height - 18;
_textField.frame = r;
} completion:^(BOOL finished)
{
//
}];
}
Facebook has released an open-source package called Three20 that has a multi-line text field. You can use this pretty easily for an expanding text field.
As for the "Done" button, you can set your view controller as a UITextFieldDelegate. Then use this method:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// Do whatever you want for your done button
return YES;
}
In the case of Three20, use this method of TTTextEditorDelegate:
- (BOOL)textFieldShouldReturn:(TTTextEditor *)textField {
// Do whatever you want for your done button
return YES;
}
Well, I had a similar problem, and what I ended up using is actually create a disabled UITextField as the background and a UITextView above it to get the input... It sucks that iPhone API cannot have this by default. Also note that this does not auto-expand, but you can do this if you want by handling the textViewDidChange:
As for handling the return key, try implementing the following method from the UITextViewDelegate:
- (void)textViewDidChange:(UITextView *)inTextView {
NSString *text = inTextView.text;
if ([text length] > 0 && [text characterAtIndex:[text length] -1] == '\n') {
inTextView.text = [text substringToIndex:[text length] -1]; // remove last return from text view
[inTextView resignFirstResponder]; // hide keyboard
}
}
(void)textEditorDidBeginEditing:(TTTextEditor *)textEditor {
And
(void)textEditorDidEndEditing:(TTTextEditor *)textEditor {
might be what you're looking for. Enjoy!