I've got a UIBarButtonItem category where I build UIBarButtonItems with custom UIButtons, since I've found UIButtons easier to customize then UIBarButtonItems.
Now, I'd like to continue to use the BarButtonItem's target and action properties instead of using those in the button so that the BarButtonItem can continue to be customized externally without anyone having to know the implementation details (i.e., that it is using a button internally).
Now, in order to do that, I'm written up this code in my category:
+ (UIBarButtonItem *)backBarButtonItemWithColor:(UIColor *)color
{
UIImage *closeIcon = [MyImageUtility navBarBackArrow];
if (color) closeIcon = [closeIcon imageWithColorOverlay:color];
UIButton *close = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, closeIcon.size.width+10.0f, closeIcon.size.height+10.0f)];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:close];
[close setImage:closeIcon forState:UIControlStateNormal];
[close addTarget:item action:#selector(SD_executeBarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
return item;
}
- (void)SD_executeBarButtonItemAction
{
[self.target performSelector:self.action];
}
Whenever the SD_executeBarButtonItemAction is called, I get a exc_bad_access on the selector, though I am not sure why. Any ideas? Is there a way around this?
Thanks!
EDIT:
here is the code being called by that selector that is crashing:
void (^transition)(void) = ^(void) {
[self.rightContainer setFrame:[self offscreenContainerFrame]];
[self.centerContainer setAlpha:1.0f]; //TODO: this is unreliable in iOS6 -- we should add a view to the top of it to darken
[self.centerContainer setTransform:CGAffineTransformIdentity];
};
[self notifyWillShowPrimaryViewController];
[self performBlock:transition animated:YES completion:^(BOOL finished) {
[self notifyDidShowPrimaryViewController];
[self setForegroundController:self.primaryNavigationController];
if (block != NULL) block(finished);
}];
Your code is a recursive call.
- (void)SD_executeBarButtonItemAction
{
[self.target performSelector:self.action];
}
You set like:
[close addTarget:item action:#selector(SD_executeBarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
Where item is a UIBarButtonItem.
Related
I have a problem in ios7(ipad), that is, the navigation bar and the bottom toolbar can not catch the tap event. The same code works fine in the ios 8 (iphone/ipad). Here is my code, I have a slideView which is a UIView, and I push another UIView singleCamView into it. The first time I load into the singleCamView, the navigation and the toolbar isn't working, but when I go back to slideView and get into the singleCamView again, everything is fine.
In slideView:
(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self changeUILayout];
[serverMgr disableTalkFunc];
if (eLayoutType == eLayout_1X1 && !backFrom1x1) {
[self changeLayout_1x1];
}
}
(void)changeLayout_1x1 {
if ([self.navigationController.visibleViewController isKindOfClass:[SlideView class]]) {
int camInArray = 0;//iPageIdx * [self getPreviewNum] + singleCamPos;
SinglePageViewController_Base *firstPage = [viewControllers objectAtIndex:0];
SingleCamViewController * mCam = [[SingleCamViewController alloc] initWithServerManager:serverMgr CurrentCam:camInArray];
[mCam setParent:firstPage];
[mCam setTitle:[serverMgr getDeviceNameByChIndex:0]];
mCam.changeLayoutDelegate = self;
int ch_index = 0;//iPageIdx * [self getPreviewNum] + pos;
[serverMgr updateAudioAndPtzDeviceByChIndex:ch_index];
[mCam setPtzCap:[serverMgr isSupportPTZ:ch_index]];
[mCam setAudioCap:[serverMgr isSupportAudio:ch_index]];
[mCam setTalkCap:[serverMgr isSupportTalk:ch_index]];
[firstPage setSingleCam:mCam];
[serverMgr setPlaybackDelegate:mCam];
[self.navigationController pushViewController:mCam animated:YES];
//clear to default picture
[firstPage setDefaultPic];
[serverMgr disconnectAllCam];
[serverMgr connectToCamWithHighResolution:ch_index];
[mCam release];
eLayoutType = eLayout_1X1;
[serverMgr setLayoutType:eLayout_1X1];
[parent save];
}
else {
return;
}
}
In singleCamView:
....
UIImage *backImage;
UIButton *bButton = [UIButton buttonWithType:UIButtonTypeCustom];
if (IS_IPAD)
backImage = [UIImage imageNamed:#"Back_48x48_nor.png"];
else
backImage = [UIImage imageNamed:#"Back_32x32_nor.png"];
bButton.bounds = CGRectMake(0, 0, backImage.size.width, backImage.size.height);
[bButton setImage:backImage forState:UIControlStateNormal];
[bButton addTarget:self action:#selector(onBackBtnTap) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem * backButton = [[UIBarButtonItem alloc] initWithCustomView:bButton];
UIBarButtonItem * backButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(#"Back", nil) style:UIBarButtonItemStylePlain target:self action:#selector(onBackBtnTap)];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
[backButton setTintColor:COLOR_NAVBUTTONTINT];
}
else {
[backButton setTintColor:COLOR_NAVBUTTONTINTIOS6];
}
self.navigationItem.leftBarButtonItem = backButton;
[backButton release];
The important thing is, it works fine in ios8, I can't figure out the reason. Thank you.
Finally I solve my own problem, here is the solution, the key point is that the second view is pushed right after the main view is created, the controller (maybe) is not fully initialized yet. So I add a delay between main view and second, everything works fine.
This post gives me the solution. Adding delay between execution of two following lines
Hi and thanks in advance for you patience and help :)
Here is what I am doing:
- I create programmatically multiple buttons that call the same function
And what I want to accomplish:
- after a button is pressed, I want the common function to run and all the buttons to be disabled so that the function won't be called again during execution of the firs call. after the first call is ended I want to reenable the buttons.
Here is how I create the buttons:
#interface ViewController ()
{
UIButton *button;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
int tagForTheButton = 0;
int yPossition = 0;
int xPossition = 0;
for (int numberOfTheColomn = 0; numberOfTheColomn < 6; numberOfTheColomn++) {
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.adjustsImageWhenHighlighted = NO;
button.frame = CGRectMake(xPossition, yPossition, 64, 64);
button.tag = tagForTheButton;
[button setTitle:title forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.scroll addSubview:button];
yPossition=yPossition+100;
tagForTheButton++;
}
}
-(void)buttonPressed:(id)sender{
//This is my attempt to disable the buttons
//previous mistake used 2578
for (int numberForIncrease; numberForIncrease<6; numberForIncrease++) {
if (button.tag == numberForIncrease) {
UIButton *btn = (UIButton*)[self.scroll viewWithTag:numberForIncrease];
btn.enabled=NO;
[btn setUserInteractionEnabled:NO];
}
}
}
You must be disabling it incorrectly. The docs for UIButton.enabled state:
If the enabled state is NO, the control ignores touch events and
subclasses may draw differently.
Check that DisableButton is being called by adding an NSLog() call, & do away with the DisableButton method altogether:
dispatch_sync(dispatch_get_main_queue(), ^{
self.view.userInteractionEnabled = NO;
_btn_sync_outlet.enabled = NO;
_btn_sync_outlet.userInteractionEnabled = NO;
});
This might helps you :)
I don't understand why you need to loop from 0 to 2577 for disabling a button which you already have an iVar pointing to..?
why don't you just switch off userInteraction to your entire view (or if that is not possible to a containerView which holds all the buttons)?
[self.view setUserInteractionEnabled: NO ];
or, failing that why not just haver an iVar there?
{
BOOL _ignoreButtons;
}
-(void)buttonPressed:(id)sender{
if (!_ignoreButtons){
_ignoreButtons = YES;
//do your time consuming job, possibly on bg thread
//on completion..
_ignoreButtons = NO;
}
}
Forgive me if this is trivial, I am still honing my programing skills. I am trying to set this button as a target. Should be easy but I dont know why it's not working! I inserted a NSLog to test and the method is not being called! Thanks for your help.
//ShareView.h
#property (strong,nonatomic) UIButton *cancelBtn;
//ShareView.m
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIImage *shareImage = [UIImage imageNamed:#"shareBox.png"];
[self setFrame:CGRectMake(10, 170, shareImage.size.width, shareImage.size.height)];
self.shareIV = [[UIImageView alloc] initWithImage:shareImage];
self.shareIV.userInteractionEnabled = YES;
[self addSubview:self.shareIV];
[self shareBtnsInit];
[self.shareIV addSubview:self.cancelBtn];
self.userInteractionEnabled = YES;
}
return self;
}
-(void)shareBtnsInit{
UIImage *cancelImg = [UIImage imageNamed:#"cancel27.png"];
self.cancelBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.cancelBtn setImage:cancelImg forState:UIControlStateNormal];
[self.cancelBtn setFrame:CGRectMake(277, 3, cancelImg.size.width, cancelImg.size.height)];
}
//MainViewController.m
-(IBAction)settingsButtonPressed:(id)sender{
self.shareVC = [[ShareView alloc] initWithFrame:CGRectMake(0, 0, 0,0)];
[self.view addSubview: self.shareVC];
[self.shareVC.cancelBtn addTarget:self action:#selector(settingsCancel:) forControlEvents:UIControlEventTouchUpInside];
}
-(IBAction)settingsCancel:(id)sender{
NSLog(#"TEST!!!");
[self.shareVC removeFromSuperview];
}
Based on comments, your settingsButtonPressed: method wasn't being called, so the button you were looking for the action on was never being set up.
Try adding [self.cancelBtn addTarget:self action:#selector(settingsCancel:) forControlEvents:UIControlEventTouchUpInside]; to your shareBtnsInit method.
Here's more information on making UIButtons programatically: How do I create a basic UIButton programmatically?
[self.cancelBtn addTarget:MainViewController action:#selector(settingsCancel:) forControlEvents:UIControlEventTouchUpInside];
In ShareView you have to import class in which you want your event should get triggered.
Here in ShareView you can import MainViewController class and can use it.
Below is the error i am getting in my app, which is working fine in ios6.
[__NSCFString frame]: unrecognized selector sent to instance 0xc075290
I am not getting what is wrong in it. But i guess something related to UINavigationController. Please guide for above.
Thanks in advance.
UPDATE: After enabling Zombies i get this error.
[_UINavigationBarBackIndicatorView frame]: message sent to deallocated instance 0xc0fb860
-(void)viewWillAppear:(BOOL)animated
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"isAcceptTerms"]) {
[adBannerView setDelegate:self];
[adBannerView setHidden:NO];
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"isBannerShown"]) //-ive logic is applied
{
[self.adBannerView setHidden:YES];
[self.adBannerView setDelegate:nil];
}
}
else
{
[adBannerView setDelegate:nil];
[adBannerView setHidden:YES];
}
[self.navigationController.navigationBar setHidden:NO];
NSMutableDictionary *dictTemp =[[sqlmessenger shared]fetchOrders];
int count=[[sqlmessenger shared] isuserdetails];
if (count>0)
{
[self updateCoordinate];
}
NSArray *arrContorl = [self.navigationController.navigationBar subviews];
for(UIButton *btnTemp in arrContorl)
{
if([btnTemp isKindOfClass:[UIButton class]])
{
[btnTemp removeFromSuperview];
}
}
UIImageView *imgHeader= [[UIImageView alloc]initWithFrame:CGRectMake(0,0,320,44)];
[imgHeader setBackgroundColor:[UIColor clearColor]];
[imgHeader setImage:[UIImage imageNamed:#"setting.png"]];
[self.navigationController.navigationBar addSubview:imgHeader];
if(lblHeader)
{
lblHeader=nil ;
}
lblHeader = [[UILabel alloc]initWithFrame:CGRectMake(60,5,230,30)];
[lblHeader setBackgroundColor:[UIColor clearColor]];
[lblHeader setTextAlignment:UITextAlignmentLeft];
[lblHeader setTextColor:[UIColor whiteColor]];
[lblHeader setFont:[UIFont boldSystemFontOfSize:18.0]];
if([dictTemp count]==0 && contentView.hidden == FALSE)
{
[lblHeader setText:#"Terms of Service (EULA)"];
}
else
{
[lblHeader setFrame:CGRectMake(110,5,200,30)];
[lblHeader setFont:[UIFont boldSystemFontOfSize:20.0]];
[lblHeader setText:#"Settings"];
}
[self.navigationController.navigationBar addSubview:lblHeader];
}
Obviously, you are trying to access the frame property of a NSString object, which is not permitted, since this object does not have this property.
Try adding more details. (Add the code that causes the crash, usually crash stack are not that helpful).
UPDATE:
Still not sure what's going on, you need to do the actual debug, plant the necessary breakpoints log your variables, see what values they have etc.
I can give you some things you can try:
1.Not sure why are you adding subviews to the navigation bar. You can instead use the navigationItem property of the UIViewController, and then leftBarButtonItem of UINavigationItem, like :
For left bar button item : (make sure you hide the back button first)
self.navigationController.navigationItem.hidesBackButton = YES;
self.navigationItem.leftBarButtonItem = yourLeftBarButtonItem;
And for the right one :
self.navigationItem.rightBarButtonItem = yourRightBarButtonItem;
2.You're allocating the view and the label each time that viewController is appearing. That's inefficient. Both memory and time-wise. Instead, you can allocate them once and change the alpha channels.
I have a grid of UIButtons. When I hit an 'edit' button, I want a delete button to appear over each of these buttons, which when pressed, deletes the button (and associated data). A bit like apple's home screen, when you hold down a button and it starts to wiggle with an X in the corner.
According to this post: Subclass UIButton to add a property I can use Associative References to add a property to each of my buttons. I've tried to add a UIButton as a property of my custom UIButton but I can't seem to get it to appear and have the feeling this isn't the right way to go. Here's my custom button main:
#import "UIButton+Property.h"
#import <objc/runtime.h>
#implementation UIButton(Property)
static char UIB_DELETEBUTTON_KEY;
#dynamic deleteButton;
- (void)setDeleteButton:(UIButton *)deleteButton {
deleteButton = [UIButton buttonWithType:UIButtonTypeInfoDark];
deleteButton.frame = CGRectMake(100, 100, 50, 50);
objc_setAssociatedObject(self, &UIB_DELETEBUTTON_KEY, deleteButton, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIButton *)deleteButton {
return (UIButton *)objc_getAssociatedObject(self, &UIB_DELETEBUTTON_KEY);
}
#end
And here's where I add the buttons programmatically:
//Create a custom button for each custom book doc
for (int i = 0; i < [customBookDocs count]; ++i) {
BookDoc *customBookDoc = [customBookDocs objectAtIndex:i];
NSString *bookTitle = customBookDoc.book.title;
//create a button for each book
CGRect frame = CGRectMake(xCoord, yCoord, 200, 200);
UIButton *bookButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
bookButton.bookDoc = customBookDoc;
[bookButton setFrame:frame];
[bookButton setTitle:bookTitle forState:UIControlStateNormal];
[bookButton addTarget:self action:#selector(bookButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
xCoord += 250;
[self.view addSubview:bookButton];
[self.view addSubview:bookButton.deleteButton];
}
Is there an easier more sensible way to do this? Or am I on the right track?
ORIGINAL RESPONSE BEGAN:
... Someone else may have more to say about that, but I'm not sure why you'd need to use object association here. You can certainly add another button to your button as a property using regular subclassing, which is the route that I would take. ...
EDITS BELOW:
I thought that I had subclassed a UI control directly, but I realized that I was mistaken when I went to look for the code. #Joe rightly pointed out in the comments that there are issues with directly subclassing UI controls.
I was able to implement something like the functionality you described without using Associated Objects, by creating a wrapper class to hold the button and its related delete button. It works, but it's not very flexible, so I would generally recommend #Joe's method as a better solution.
Here's the relevant code:
I threw all of the code into the appDelegate to keep it simple. I don't recommend that in real life.
AppDelegate.m:
#implementation AppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
UIButton *toggleDeleteButtons = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[toggleDeleteButtons setFrame:CGRectMake(20, 45, 280, 45)];
[toggleDeleteButtons setTitle:#"Toggle Delete" forState:UIControlStateNormal];
[toggleDeleteButtons addTarget:self action:#selector(toggleDeleteButtonAction) forControlEvents:UIControlEventTouchUpInside];
[[self window] addSubview:toggleDeleteButtons];
ButtonWrapper *myButtonWrapper = [[ButtonWrapper alloc] init];
[[myButtonWrapper button] setFrame:CGRectMake(20, 100, 200, 45)];
[[myButtonWrapper button] setTitle:#"This is my button" forState:UIControlStateNormal];
[[myButtonWrapper deleteButton] addTarget:self action:#selector(buttonDeleteRequested:) forControlEvents:UIControlEventTouchUpInside];
[[myButtonWrapper deleteButton] setTag:0];
[[self window] addSubview:[myButtonWrapper button]];
buttonWrapper1 = myButtonWrapper;
// Added instance called anotherButtonWrapper with tag 1, as above
// Added instance called stillAnotherButtonWrapper with tag 2, as above
[self.window makeKeyAndVisible];
return YES;
}
- (void)toggleDeleteButtonAction {
static BOOL deleteButtonsShown;
[buttonWrapper1 showDeleteButton:!deleteButtonsShown];
[buttonWrapper2 showDeleteButton:!deleteButtonsShown];
[buttonWrapper3 showDeleteButton:!deleteButtonsShown];
deleteButtonsShown = !deleteButtonsShown;
}
- (void)buttonDeleteRequested:(UIButton *)deleteButton {
// delete the specified button here
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Delete" message:[NSString stringWithFormat:#"Delete was pressed on button %i",[deleteButton tag]]delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
ButtonWrapper.m:
#implementation ButtonWrapper
#synthesize button;
#synthesize deleteButton;
- (ButtonWrapper *)init {
ButtonWrapper *newWrapper = [ButtonWrapper alloc];
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[myButton setFrame:CGRectZero];
UIButton *myDeleteButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[myDeleteButton setFrame:CGRectMake(0, 0, 100, 40)];
[myDeleteButton setTitle:#"Delete" forState:UIControlStateNormal];
[myDeleteButton setHidden:TRUE];
[myButton addSubview:myDeleteButton];
[newWrapper setButton:myButton];
[newWrapper setDeleteButton:myDeleteButton];
return newWrapper;
}
- (void)showDeleteButton:(BOOL)showButton {
if (showButton) {
[[self deleteButton] setHidden:FALSE];
[[self deleteButton] setEnabled:TRUE]; }
else {
[[self deleteButton] setHidden:TRUE];
[[self deleteButton] setEnabled:FALSE];
}
}
#end
This solution did not require me to implement all of the UI properties, but it did require extra work to hook up the embedded delegates, which is cumbersome. There may be a way to pass the delegates into the wrapper at initialization, but I couldn't make it work.