Making a custom Button using a UIView or overriding UIButton? - iphone

I need to generate a custom button through code, this is how i am currently doing it.
-(void) initialiseButtons
{
int ypos = playerImage.frame.origin.y + playerImage.frame.size.height + 8;
for(int i=0; i<totalButtons; i++)
{
UIButton *newButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[newButton setFrame:CGRectMake(20, ypos, 220, 30)];
newButton.tag = 10 + i;
[newButton addTarget:self action:#selector(statTapped:) forControlEvents:UIControlEventTouchUpInside];
[self.frontView addSubview:newButton];
ypos += 30 + 7;
}
}
This creates the blank buttons perfectly through code, gives them a tag and assigns an callback function on touchUpInside.
The custom button needs to be able to handle showing an image when pressed down.
It needs to be able to draw 2 pieces of Text. 1 aligned to the left hand side of the button and 1 aligned to the righthand side of the button.
My boss suggested instead of buttons I use a View. I dont understand how this will work. When i start thinking about it, i think it would require having a viewcontroller dedicated to the buttons. And some draw method? It sounds complicated and I am not grasping how it can be done.
Is there a simpler method by making a custom class overriding UIButton? I made a simple test class earlier but nothing was drawn in the buttons locations when I used them in place of the Normal UIButton class. I expected i would see the buttonUp.png drawn.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface CardButton : UIButton {
}
#end
#import "CardButton.h"
#import <UIKit/UIKit.h>
#implementation CardButton
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self setImage:[UIImage imageNamed:#"buttonUp.png"] forStates:UIControlStateNormal];
self.adjustsImageWhenHighlighted = YES;
}
return self;
}
#end
Anybody able to give me some pointers on this? I'm pretty blocked at the moment. Have been reading some various other threads related to buttons but nothing that made it clear in my head how to tackle the problem

Personally, I would subclass UIButton. Despite all the talk that you can't subclass UIButton, Apple even talks about subclassing UIButton in the UIButton documentation.
In the subclass I would create a UIView with the two labels (or images or whatever) and add them as subviews to the button (be sure to set interactive for the text and view as FALSE).
What is awesome about this is that it leverages the UIButton code and keeps you from reinventing the wheel.

You should not (I'd almost say you can't) subclass UIButton, as it turns out to be a class cluster, and it would be impractical (read: impossible) to derive from that.
So your two options are:
1) Use standard UIButton of custom type, add the elements you want to show (i.e. UILabel) and hook up actions for touch down and touch up and react accordingly (change the views, trigger actions etc.)
2) Use a custom UIView, implement drawRect: to draw how you like it or use custom views as subviews as in 1). Then use the touchesBegan:, touchesEnded: etc. messages to react accordingly (or UIGestureRecognizer if you can live with 3.2+ compatibility).
You might build a factory to build those buttons if they all are very similar, so that your code becomes clean and non-repetive.
Edit as per iOS 6:
Apple now mentions subclassing in the UIButton docs, so while still not very well defined, it seems quite safe now to do so.

An easy way to do this, is to override a UIView. In the view you add a UIButton as Subview. This way you have a reusable class without the need to re-implement button behaviour.
You can style your button the way you want in the initWithFrame method of your UIView derived class. If you use this class as target for your button events, you can implement special behaviour easily, like showing the image.
For the two pieces of text, you create two labels and add them as subviews to the button.

Related

How to make multiple UIButtons connect to the same IBAction?

I need several UIButtons to all connect to the same IBAction so the buttons can all do the same thing without having to copy and paste the code. Please tell me if there is a way to do this! It might be right under my nose, but I can't find it. Thanks!
Simply add the same target and selector to each button:
[button1 addTarget:self
action:#selector(buttonClicked:)
forControlEvents:UIControlEventTouchUpInside];
[button2 addTarget:self
action:#selector(buttonClicked:)
forControlEvents:UIControlEventTouchUpInside];
// etc.
For this You need to use set IBOutlet for Each Button or Set tag for each button if you are using Outlet then used this code .h
#interface RootViewController_Phone : UIViewController
{
IBOutlet UIButton *btn1;
IBOutlet UIButton *btn2;
}
- (IBAction)buttonPressed:(id)sender;
-(void)CallButtonsMethod;
#end
Now in .m file
- (IBAction)buttonPressed:(id)sender{
if([sender isEqual:btn1])
{
[self CallButtonsMethod];
}
if([sender isEqual:btn2])
{
[self CallButtonsMethod];
}
}
-(void)CallButtonsMethod
{
//your Code
}
Use IBOutletCollections.
Here is tutorial for that:
http://useyourloaf.com/blog/2011/03/28/interface-builder-outlet-collections.html
I've found that I'm usually not able to hook up multiple buttons (or even a single button, for that matter) in IB to an already existing IBAction in code, by Ctrl-dragging from the button to the IBAction in the m file. Xcode tries to create a new action in the dragged-to file, but won't connect to an existing action. [This is true for Xcode 4.6.1 and several previous (maybe all) versions.]
The approach in the accepted answer in the following link works if the IBAction is in the view controller which is the "File Owner" for the view containing the button:
Connect object in .xib to existing IBAction
However if you want your IBAction to be in the view's own class, rather than the view controller's class, then the following approach works:
Drag only one button onto the canvas in IB first. Control-drag from
the button to the view's .m file.
Drop the dragged line in any vacant space in the implementation section of the code.
Define a new IBAction in the little popup dialogue box. So now you have one
button hooked up to an IBAction.
Now instead of dragging more buttons from the object library, hold
down the Option key and drag out a new button from the original one.
This new button will come hooked up to the same IBActions as the
original one. (If you need to create lots and lots of buttons and
this seems tedious, you can select a bunch of already created ones simultaneously and
Option-drag to create as many more in a single go.)

Can I *replace* the UILabel on a UIButton with FXLabel?

So, I'm trying to change the UILabel on a UIButton to an instance of an FXLabel instead. Check out the link for more on FXLabel (https://github.com/nicklockwood/FXLabel). I know by using class extensions (Subclass UIButton to add a property), I could always add another property, in my case, an FXLabel by just adding a subview basically. However, I like the convenience and features of the titleLabel property already present.
Is there some method to "switch" the UILabel for an FXLabel by subclass or category?
I do know I could do this, but it's such a hack and isn't future proof. And I don't know how to then change the class type (Get UILabel out of UIButton).
UILabel *label;
for (NSObject *view in button.subviews) {
if ([view isKindOfClass:[UILabel class]]) {
label = (UILabel *)view;
break;
}
}
Thoughts?? Thanks.
Here's another way you could at least access the label. Create a subclass of UIButton, and in that subclass override layoutSubviews. From there you can access the label directly:
- (void)layoutSubviews {
[super layoutSubviews];
UILabel *titleLabel = [self titleLabel];
}
Now we're still stuck with the problem of changing that label to be a subclass. I guess you could try to dynamically swap the UILabel's class with your FXLabel subclass (as detailed here), but to be honest that's pretty risky.
I'd say you're better off just creating your own UIControl that you can customize to your hearts content. No risk, just pure goodness. It shouldn't be too hard to imitate a simple UIButton. Good luck!
Sorry to say it but I don't think you can do this in any reliable way. Even if you subclassed UIButton and overrode the titleLabel methods you'd also have to handle the state settings. And you never know when the internal code refers directly to a private ivar for the label.
I created a subclass of UIButton for my apps to do something similar (custom button layouts & subviews), sorry I can't share it right now, it's owned by a client. It's not too difficult to create your own state handling code for your own subviews.

How can I set the background image of multiple buttons in a certain view from a different view?

Basically, I cannot figure out how to change the background image. I have searched and searched and just cannot seem to find it. Anyone have any ideas? Thanks!
Update
Here is the code I use to show the View:
SettingsViewController *settingsView = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:settingsView animated:YES];
If anyone needs anything else to help out I'll do my best! Thanks!
Passing a messages between different views can be done by direct call of the methods (not a good architectural solution but maybe not critical for a small projects) or with an event driven model as described in an answer above.
As for binding of a collection of objects and processing the afterward I recommend to review IBOutletCollection keyword that allows to bind multiple objects fro an InterfaceBuilder to a property with a type like NSArray.
Property declaration will look like following:
#property (nonatomic, retain) IBOutletCollection(UIButton) NSArray *buttons;
The code to change the background for all the buttons will look like following:
UIImage *backgroundImage = [UIImage imageNamed:#"background.png"];
for(UIButton *button in buttons) {
[button setBackgroundImage:backgroundImage forState:UIControlStateNormal];
}
You use setBackgroundImage:forState: to set the image.
Regarding the different views part, it depends on how you wrote your code. If the view with the buttons is controlled by the different view (by creating an instance and using addSubview:) then you can call it directly by using instanceName.buttonName (as long as you declare it as a property -thanks fichek).
If you don't manually add the view, instead through IB, you can have the button that controls the other button's image point to the IBAction in that class.
If neither of those options work you can always use NSNotificationCenter.

How to tint UIBarButtonItem background color? [duplicate]

This question already has answers here:
UIToolbar tint on iOS 4
(2 answers)
Closed 3 years ago.
I have a UIToolbar that contains 2 buttons. The toolbar has a tint:
toolbar.tintColor = [UIColor colorWithRed:(102.0/255.0) green:(20.0/255.0) blue:(11.0/255.0) alpha:1];
How can I make the buttons have a similar tint color?
I found this solution preferable to those listed here: Tint UIButton and UIBarButtonItem. Unlike the accepted answer, it allows you to change the color of UIBarButtonItems independent of the UINavigationBar.
In case the link goes down in the future, the gist of it is that you create a tinted UISegmentedControl (with UISegmentedControlStyleBar) with one segment, then create a UIBarButtonItem using that as its custom view.
In iOS 5, UIBarButtonItem has a tintColor property.
The best thing to do is set the tintColor AFTER you add buttons to it, as in iOS 4.0, it no longer updates buttons added to the bars after the tintColor has been set.
see "Changing colors of UINavigationBarButtons"
EDIT: I remove the link because the domain is down...
The is the text from google cache:
Alright, here’s another quick tip. “How to change the colors of a button on a toolbar.” Of course, this can be applied to any toolbar but I am going to demonstrate the procedure on a UINavigationBar.
The above image only shows a couple of colors. In truth, you can make the button any color that you want. Fantastic! The code is really simple to do this as well. The first thing that we want to do is open the header file for whichever object will be turning a nav bar button a different color and declare the forward class UINavigationButton. You can get this class by either iterating through the subviews of the UINavigationBar, reading its subviews class names, or by class-dumping UIKit if you have a jailbroken device.
Place the following line before your interface declaration:
#class UINavigationButton;
Now, declare a new method in the header that we will use to actually change the button’s color.
- (void)changeNavigationButtonColorToColor:(UIColor *)newColor
Or something similar to the above line of code.
Now, open up your object’s implementation file and implement the above method. Anywhere in your file, add the following method:
- (void)changeNavigationButtonColorToColor:(UIColor *)newColor {
for (UIView *view in self.navigationController.navigationBar.subviews) {
NSLog(#"%#", [[view class] description]);
if ([[[view class] description] isEqualToString:#"UINavigationButton"]) {
[(UINavigationButton *)view setTintColor:newColor];
}
}
}
As you can see above, this is actually a lot easier than it first appears to be. What we first do is set up a for loop to iterate through the subviews of the UINavigationBar using NSFastEnumeration. We then output the class name of the subview, for future reference. IF the class name is UINavigationButton, then we’ve got our view. All we do is set the tintColor property if the UINavigationButton.
That’s it, we’re done!
Alternatively, if you want a wider scope, I’d suggest creating a new UINavigationBar category and placing the button color changing method in there. This was your method can be performed by any class that uses a UINavigationBar without having to recreate the same method over and over.
Remember, a back button and a navigation button are not the same thing. You will have to color the back button separately.
And as usual, here’s a link to a sample app that demonstrates this code: NavButtonColor.zip
UIBarButtonItems inherently respond to setTintColor, though it's not public API.
I have a custom UINavigationBar subclass that runs this block:
for (UIView *subview in self.subviews) {
if ([subview respondsToSelector:#selector(setTintColor:)]) {
[subview performSelector:#selector(setTintColor:) withObject:[UIColor redColor]];
}
}
Obviously replace [UIColor redColor] with the color of your choosing.
Available in iOS 5.0 and later for UIBarButtonItem:
- (void)setBackgroundImage:(UIImage *)backgroundImage forState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics
It seems to be the only current solution (IOS 9.0).
tintColor is now used for the Image Color.
Swift3
UIBarButtonItem.appearance().tintColor = UIColor.green

How do I prevent text entry in a UITextView?

I have a text field entry in my view that I would like to block access to during a background operation. I've tried using the editable property, which successfully blocks access during the background operation, but the moment I set editable to YES, the keyboard comes up and the textfield becomes the first responder. Dismissing the keyboard just after changing editable doesn't do anything:
// Broken code
textView.editable = YES;
[textView resignFirstResponder];
I've thought about adding a clear UIView that just blocks access to the UITextView after dismissing the keyboard, but that seems like overkill. Is there a correct way to handle this?
Just so people don't have to read farther than the selected answer: It turns out that this is a "known issue" in the SDK, and you can find it listed in the release notes. Using userInteractionEnabled performs the same function, as long as you make sure to dismiss the keyboard yourself.
Try textView.userInteractionEnabled = NO;
Put a UIView in front of the UITextView with a dark (or white) background color and alpha set low (like 5%) sized to fully cover the textview. Default it to hidden.
When you want the textinput disabled, send it a resignFirstResponder then show the hidden layer on top. It intercepts user inputs (and ignores it). The alpha color will make it look 'dimmed.' Once your background operation is done just set the cover view to hidden and you're good to go. If you want to get fancy you can do UIView alpha fade animations.
I'm not sure of a "correct way" but I'd probably do the transparent view solution... I agree that it seems like overkill but the simple solution is often a good answer.
Since the view gets focus upon changing the editable properties this would be easier.
the other solution that I can think of is to derive a custom UITextView and recode the editable property (or make a new method) that can accomplish what you are trying to do. This is a good object oriented solution but this could be come cumbersome.
You might also consider using a Category to add the functionality. But for either of these solutions, are still back to square one of how to accomplish what you need...
Thank god someone came up with a better response. I originally built it with the following blocker, but the userInteractionEnabled BOOL is much easier.
It turns out that the problem is a known issue with UITextView. My workaround:
#import <UIKit/UIKit.h>
/**
God damn SDK bug means that you can't use editable to enable/disable a
UITextView (It brings up the keyboard when re-enabling)
*/
#interface StupidF_ingTextViewBlocker : UIView {
}
#end
#implementation StupidF_ingTextViewBlocker
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
}
return self;
}
- (void)dealloc {
[super dealloc];
}
#end
Then in place of the code I've placed above (instead of using editable). To disable (assuming I have an iVar called blocker):
// Put this in a lazy loading property or in loadView
blocker = [[StupidF_ingTextViewBlocker alloc] initWithFrame:writeView.frame];
// The blocker code ~= textView.editable = NO
[self.view addSubview:blocker];
// Removing the blocker ~= textView.editable = YES
[blocker removeFromSuperView];
Subclass UITextView, and implement a single method:
- (BOOL) canBecomeFirstResponder { return NO; }
Use the UITextViewDelegate. You'll need to swap out the delegate object depending on the current state.
If in the blocked state, then you'll use a delegate where textViewShouldBeginEditing returns NO.
the other delegate's textViewShouldBeginEditing would return YES.