Can I update a label in a UIButton without redrawing the button? - objective-c

I want a clock readout to appear as a label in a UIButton once every second. But even though I remove it from the superView the new UIButton overwrites the old one, like this
… and after several minutes of this my iPhone looked seriously burnt :-)
In PlayViewController my NSTimer methods look like this
- (void)startClock {
clockCount = 0; // start on 1st clock pulse
totalMinutes = 0; // appears on clockButton
totalSeconds = 0; // appears on clockButton
timer = [NSTimer scheduledTimerWithTimeInterval:STATES_ConcertClock
target:self
selector:#selector(nextClock)
userInfo:nil
repeats:YES];
}
- (void)nextClock {
self.lastEventChangeTime = [NSDate date];
clockCount++;
[self masterClockReadout];
}
and here is my clock readout method
- (void)masterClockReadout {
totalMinutes = clockCount / 60;
totalSeconds = clockCount % 60;
clockString = [NSString stringWithFormat:#"%02d:%02d", totalMinutes, totalSeconds];
[self.seconds removeFromSuperview];
EscButton *seconds = [[EscButton alloc] loadEscButton:(NSString *)clockString];
[self.view addSubview:seconds];
}
I also set a UIView property so removeFromSuperview knows what to remove.
#property (nonatomic, retain) UIView* seconds;
My question is: can I update the UIButton label without redrawing the button ? and, is this a problem that might be solved by using a delegate ?
To date my experience using delegates has been for sending messages from the UIButton to ViewController(e.g. below) but so far I haven't found an example I am able to apply where messages are sent in the opposite direction. So if using a delegate is the recommended approach, could you please point me to some code that might help me solve this problem. Thanks
EscButton.h
#import <UIKit/UIKit.h>
#protocol EscButtonDelegate <NSObject>
-(void)fromEscButton:(UIButton*)button;
#end
#interface EscButton : UIView {
}
- (id)loadEscButton:(NSString *)text;
#property (assign) id<EscButtonDelegate> delegate;
#end
EscButton.m
#import "EscButton.h"
#implementation EscButton
- (id)loadEscButton:(NSString *)text {
CGFloat sideOffset = screenWidth - ESC_BUTTON_Width - MARGIN_Side;
CGFloat topOffset = statusBarHeight + MARGIN_Top;
UIButton *escButton = [UIButton buttonWithType:UIButtonTypeCustom];
escButton.frame = CGRectMake(sideOffset, topOffset, ESC_BUTTON_Width, ESC_BUTTON_Height);
// etc …
[escButton addTarget:self.delegate action:#selector(fromEscButton:) forControlEvents:UIControlEventTouchUpInside];
[escButton setTitle:text forState:UIControlStateNormal];
// etc …
return escButton;
}
#end

There is no need to constantly add and remove a button. You can keep the same one. Change the following code:
#property (nonatomic, retain) UIView* seconds;
to:
#property (nonatomic, retain) UIButton *secondsB;
Also change:
[self.seconds removeFromSuperview];
EscButton *seconds = [[EscButton alloc] loadEscButton:(NSString *)clockString];
[self.view addSubview:seconds];
to:
if (!self.secondsB) {
self.secondsB = [[EscButton alloc] loadEscButton:(NSString *)clockString];
[self.view addSubview:_secondsB]; // previously addSubview:seconds];
}
[self.secondsB setTitle:clockString forState:UIControlStateNormal];

Related

GPUImage animated gaussian blur filter

I was using the GPUImage gaussian blur filter to blur a still image. I want to tie the blur size to an UI element and as the element is changed by the user I blur the picture. The way I am doing it right now is to change the blurSize when there has been a significant (> 0.25) change, reapply the filter and animate the new image into the imageView.
Is there a more efficient way for me to be doing this?
On the iPhone 5, while performance is not laggy, it is not super smooth either (but perhaps that is because this operation is simply too expensive to be super smooth).
interesting question. Your best bet is to pre-compute and render CGImages at a couple of blur scales in your range, then use two stacked UIImageViews, with the one on top showing the blurrier copy but partially transparent. For example, if you rendered blurred at radiuses 1,2,3,…,10, and you wanted to show 3.7, you'd show image 3 on the bottom and image 4 on the top, at 70% opacity.
// Created in answer to Nikhil Varma's question at http://stackoverflow.com/questions/18804668/gpuimage-animated-gaussian-blur-filter
#import "GPUImage.h"
#define BLUR_STEP 2.
#interface AHViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *imageView2;
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#property (nonatomic) CGFloat blurRadius;
#property (nonatomic, strong) UIImage *image;
#property (nonatomic, strong) GPUImageGaussianBlurFilter *blurFilter;
#property (nonatomic, strong) NSCache *blurredImageCache;
#property (nonatomic, strong) dispatch_queue_t blurQueue;
#property (nonatomic, strong) NSMutableSet *blurAmountsBeingRendered;
#end
#implementation AHViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.image = [UIImage imageNamed:#"ph.jpg"];
self.blurFilter = [GPUImageGaussianBlurFilter new];
self.blurredImageCache = [NSCache new];
self.blurQueue = dispatch_queue_create("Image Blur Queue", DISPATCH_QUEUE_SERIAL);
self.blurAmountsBeingRendered = [NSMutableSet set];
self.blurRadius = 1.0;
}
- (IBAction)sliderDidMove:(UISlider *)sender {
self.blurRadius = 10 * sender.value;
}
-(void)setBlurRadius:(CGFloat)blurRadius {
_blurRadius = blurRadius;
CGFloat smaller = self.blurRadius - fmodf(self.blurRadius, BLUR_STEP);
[self asyncGenerateImageWithBlurAmount:smaller];
CGFloat larger = smaller + BLUR_STEP;
[self asyncGenerateImageWithBlurAmount:larger];
}
-(UIImage *)cachedImageWithBlurAmount:(CGFloat)blur {
return [self.blurredImageCache objectForKey:#(blur)];
}
-(void)asyncGenerateImageWithBlurAmount:(CGFloat)blur {
// This image is already available.
if([self.blurredImageCache objectForKey:#(blur)]) {
[self imageIsAvailableWithBlur:blur];
return;
}
// There's already a render going on for this. Just return.
if([self.blurAmountsBeingRendered containsObject:#(blur)])
return;
// Start a render
[self.blurAmountsBeingRendered addObject:#(blur)];
dispatch_async(self.blurQueue, ^{
self.blurFilter.blurSize = blur;
UIImage *result = [self.blurFilter imageByFilteringImage:self.image];
[self.blurredImageCache setObject:result forKey:#(blur)];
[self.blurAmountsBeingRendered removeObject:#(blur)];
dispatch_async(dispatch_get_main_queue(), ^{
[self imageIsAvailableWithBlur:blur];
});
});
}
-(void)imageIsAvailableWithBlur:(CGFloat)blurAmount {
CGFloat smaller = self.blurRadius - fmodf(self.blurRadius, BLUR_STEP);
CGFloat larger = smaller + BLUR_STEP;
UIImage *sharperImage = [self cachedImageWithBlurAmount:smaller];
UIImage *blurrier = [self cachedImageWithBlurAmount:larger];
if(sharperImage && blurrier) {
if(![self.imageView.image isEqual:sharperImage])
self.imageView.image = sharperImage;
if(![self.imageView2.image isEqual:blurrier]) {
self.imageView2.image = blurrier;
}
self.imageView2.alpha = (self.blurRadius - smaller) / BLUR_STEP;
}
}
#end

Objective-c floating button, same procedure different behaviour

I have a created a floating button on a UITableViewController. It works like expected.
Now I wanted to use the button as well on a UIViewController where I added a UITableView.
The code for creating the floating button is identically, but the behaviour is different.
At the UITableViewController the button stay where it shall.
At the UIViewController the button moves up (when I scroll down) and down (when I scroll up)
How do I get the button stay where it shall on the UIViewController. Like on the UITableViewController?
TableViewController.h
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "SingletonClass.h"
#interface MagTableViewController : UITableViewController <UIGestureRecognizerDelegate>
#property (strong, nonatomic) IBOutlet UITableView *magTableView;
#property (strong, nonatomic) UIButton *oButton;
#property (nonatomic) float originalOrigin;
#end
TableViewController.m - floating button, works like expected
- (void)viewDidLoad
{
[super viewDidLoad];
SingletonClass *sharedSingleton = [SingletonClass sharedInstance];
self.originalOrigin = sharedSingleton.photoButtonY; // 430.0
[self addOverlayButton];
}
#pragma mark Camera Button
- (void)addOverlayButton {
self.oButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.oButton.frame = CGRectMake(132.0, self.originalOrigin, 56.0, 56.0);
[self.oButton setImage:[UIImage imageNamed:#"CameraButton56x56.png"] forState:UIControlStateNormal];
[self.oButton addTarget:self action:#selector(toCameraView:) forControlEvents:UIControlEventTouchUpInside];
[self.view insertSubview:self.oButton aboveSubview:self.view];
}
// to make the button float over the tableView including tableHeader
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGRect tableBounds = self.tableView.bounds; // should be _magTableView, but works with _tableView as well. But I do not know where this _tableView comes from...
//CGRect tableBounds = self.magTableView.bounds;
// floating Button;
CGRect floatingButtonFrame = self.oButton.frame;
floatingButtonFrame.origin.y = self.originalOrigin + tableBounds.origin.y;
self.oButton.frame = floatingButtonFrame;
[self.view bringSubviewToFront:self.oButton]; // float over the tableHeader
}
ViewController.h
#import <UIKit/UIKit.h>
#import "SingletonClass.h"
#interface DetailViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) UIButton *oButton;
#property (nonatomic) float originalOrigin;
#end
ViewController.m - Jumping Button, not wanted behaviour (Update: works no, no jumping anymore)
- (void)viewDidLoad
{
[super viewDidLoad];
self.originalOrigin = [[SingletonClass sharedInstance] photoButtonY]; // 430.0
[self addOverlayButton];
[self initTableView]; // initiate the tableview and works properly
}
#pragma mark Camera Button
- (void)addOverlayButton {
self.oButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.oButton.frame = CGRectMake(132.0, self.originalOrigin, 56.0, 56.0);
[self.oButton setImage:[UIImage imageNamed:#"CameraButton56x56.png"] forState:UIControlStateNormal];
[self.oButton addTarget:self action:#selector(toCameraView:) forControlEvents:UIControlEventTouchUpInside];
[self.view insertSubview:self.oButton aboveSubview:self.view];
}
/** Remove this function and the button stayed where I expected it
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGRect tableBounds = self.tableView.bounds;
// floating Button;
CGRect floatingButtonFrame = self.oButton.frame;
floatingButtonFrame.origin.y = self.originalOrigin + tableBounds.origin.y;
self.oButton.frame = floatingButtonFrame;
[self.view bringSubviewToFront:self.oButton]; // float over the tableHeader
}
*/

Clickable search icon in NSSearchField and fading background

I would like to make an NSSearchField, just like the one in Ecoute 3.0.
The search icon in the search field should be clickable and expand and collapse if you click it.
How can you achieve this?
I have searched the documentation, but didn't come up with anything useful.
I could implement the expanding with the animator proxy, so that is clear.
Another thing is that the background fades in/fades out as it expands/collapses.
How can I achieve this?
I would be thankful for any advices, sample code, etc.
EDIT
Ok I found the first part out myself.
- (void) resetSearchButtonCell {
NSButtonCell *c= [[NSButtonCell alloc] init];
[c setButtonType:NSMomentaryChangeButton]; // configure the button
[c setBezelStyle:NSRegularSquareBezelStyle]; // configure the button
[c setBordered:NO];
[c setBezeled:NO];
[c setEditable:NO];
[c setImagePosition:NSImageOnly];
[c setImage:[NSImage imageNamed:#"search"]];
[c setAlternateImage:[NSImage imageNamed:#"search-pushed"]];
[c setTarget:self];
[c setAction:#selector(_search:)];
[self setSearchButtonCell:c];
}
Now, I have made a property isCollapsed, which I toggle in _search:.
And in the setIsCollapsed: setter method I do the following:
NSRect newRect = self.frame;
if (_isCollapsed) {
newRect.size.width = kCollapsedWidth;
} else {
newRect.size.width = kEXpandedWidth;
}
[[self animator] setFrameSize:newRect.size];
But for some reason the frame is being reset to the initial size, the one set in Interface Builder, when the NSSearchField gets or looses focus.
Can anyone tell me why?
EDIT
I have solved that problem too. It was auto-layout, which messed up the animation.
Now, the only thing remaining is the background-alpha, which should change.
I could redraw the entire thing, but is there another solution where I could just modify the alpha of only the background and the cancel-button?
I wanted to do something similar. ITSearchField is very solid and nice. However, I desired to expand the search field once it gained focus. As one might find out quickly the NSSearchfield is comprised of many components and layers.
Here is what I wrote to get it to work...
Note: It works on 10.7 or greater. Also you must hook-up the layout constraint of the search field control's width in IB to the outlet.
.h
#interface MySearchField : NSSearchField
#end
.m
#pragma mark - Implementation: Search Field Control
#interface MySearchField()
#property (readwrite, nonatomic, getter=isExpanded) BOOL expand;
#property (readwrite, nonatomic, getter=hasFocusRing) BOOL focusRing;
#property (readwrite, strong, nonatomic) NSTimer *expandTimer;
#property (readwrite, strong, nonatomic) IBOutlet NSLayoutConstraint *widthConstraint;
#property (readwrite, nonatomic) CGFloat searchWidth;
#end
static NSTimeInterval const kSearchFieldTime = 0.5;
#implementation MySearchField
#synthesize expand, focusRing;
#synthesize expandTimer, searchWidth;
#synthesize widthConstraint;
#pragma mark Timer Methods:
- (void) resizeSearchField {
if ( self.hasFocusRing ) {
if ( !self.isExpanded ) {
self.searchWidth = self.widthConstraint.constant;
[[self.widthConstraint animator] setConstant:(self.searchWidth * 2)];
self.expand = YES;
} } else {
if ( (self.isExpanded) &&
((!self.stringValue) || ([self.stringValue isEqualToString:#""])) ) {
[[self.widthConstraint animator] setConstant:self.searchWidth];
self.expand = NO;
} }
}
- (void) stopTimer {
if ( self.expandTimer )
[self.expandTimer invalidate];
self.expandTimer = nil;
}
- (void) startTimer {
[self stopTimer];
SEL resizeSelector = #selector(resizeSearchField);
NSMethodSignature *expandSignature = [MySearchField instanceMethodSignatureForSelector:resizeSelector];
NSInvocation *expandInvocation = [NSInvocation invocationWithMethodSignature:expandSignature];
[expandInvocation setSelector:resizeSelector];
[expandInvocation setTarget:self];
self.expandTimer = [NSTimer scheduledTimerWithTimeInterval:kSearchFieldTime invocation:expandInvocation repeats:NO];
}
#pragma mark Override Methods:
- (void) noteFocusRingMaskChanged {
self.focusRing = !NSEqualRects(NSZeroRect, self.focusRingMaskBounds);
[self startTimer];
[super noteFocusRingMaskChanged];
}
#end
Ok that's it really.
You get notified when the focus ring changes and start a quick timer method.
The timer is necessary for when you click the search field's cancel button which resets the control.
The only thing the timer does is fire the resizeSearchField method.
Update: Oh yeah, don't forget to set the class of your Search Field control in IB to MySearchField or whatever you want to call it.
Enjoy!
I ended up redrawing the background. Like this I was able to set the alpha of it.
The source code is available on Github:
https://github.com/iluuu1994/ITSearchField

UIButton not working in a custom UIImageView

I am using a custom view and there is a UIButton in it but it is not working. Of course the button is inside the custom view. It's driving me crazy. I tried by self.isUserInteractionEnabled = YES;, but getting an error "No setter method 'setIsUserInteractionEnabled:' for assignment". Can anyone help...
My header file...
#import <Foundation/Foundation.h>
#import "Clone.h"
#class UICloneInfoView;
#interface UICloneInfoView : UIImageView
{
Clone *clone;
}
#property(nonatomic, retain) Clone *clone;
#property(nonatomic, retain) UIButton *button;
#property(nonatomic, retain) UILabel *infoLabel;
// set a clone to show information on the bar
- (void)setClone:(Clone *)aClone;
#end
Here is the .m file...
#import "UICloneInfoView.h"
#define DEFAULT_HEIGHT_VIEW 90.0f
#define DEFAULT_WIDTH_VIEW 819.0f
#define DEFAULT_BUTTON_SIZE 40.0f
#implementation UICloneInfoView
#synthesize clone = _clone;
#synthesize button = _button;
#synthesize infoLabel = _infoLabel;
- (id)initWithImage:(UIImage *)image
{
self = [super initWithImage:image];
if (self) {
// init somthing
// init info label
_infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 5.0f, DEFAULT_WIDTH_VIEW - 80.0f, DEFAULT_HEIGHT_VIEW)];
_infoLabel.backgroundColor = [UIColor clearColor];
_infoLabel.font = [UIFont boldSystemFontOfSize:13.0f];
_infoLabel.lineBreakMode = UILineBreakModeCharacterWrap;
_infoLabel.numberOfLines = 3;
// init button
_button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
_button.frame = CGRectMake(_infoLabel.frame.origin.x + _infoLabel.frame.size.width + 10.0f, (self.frame.size.height/2), DEFAULT_BUTTON_SIZE, DEFAULT_BUTTON_SIZE);
[_button setTitle:#"1" forState:UIControlStateNormal];
[_button addTarget:self action:#selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_button];
}
return self;
}
- (void)setClone:(Clone *)aClone
{
_clone = aClone;
// set label text
_infoLabel.text = [NSString stringWithFormat:#"Start Line: %d\tEnd Line: %d\t nLines: %d\tpcid: %d\nFile: %#", _clone.startLine, _clone.endLine, _clone.endLine - _clone.startLine + 1, _clone.pcid, _clone.sourcePath];
// adjust the label with new height
CGSize expectedLabelSize = [_infoLabel.text sizeWithFont:_infoLabel.font constrainedToSize:_infoLabel.frame.size lineBreakMode:_infoLabel.lineBreakMode];
CGRect newFrame = _infoLabel.frame;
newFrame.size.height = expectedLabelSize.height;
_infoLabel.frame = newFrame;
// add _infoLabel to view
[self addSubview:_infoLabel];
}
- (void)buttonAction:(id)sender
{
NSLog(#"%#", ((UIButton*)sender).titleLabel.text);
}
#end
Here is a snapshot
Thanks in advance.
UIImageView has userInteractionEnabled set to NO by default. You are adding the button as a subview to the image view. You should set it to YES.
It might be because your class is a subview of UIImageView, so the image view is handling all the touch events, and they are never reaching your button. You could play around with disabling user interaction on the image view so that the touch event gets to the button.
A far simpler solution would just be to make UICloneInfoView a subclass of UIView instead. Then you can add your button, your labels, Clone and then just add a UIImageView as a subview.
Is there any reason you wanted to subclass UIImageView? I don't know your requirements.

Don't understand why I'm receiving EXC_BAD_ACCESS in xCode

I don't understand why I'm getting a EXC BAD ACCESS error when I call the mineHit method in my .m file. I understand that it's indicating that the button array has been released, but I don't understand why It would have been released at all.
#import "basicsViewController.h"
#implementation basicsViewController
#synthesize resetGame;
#synthesize scoreLabel;
#synthesize timeLabel;
#synthesize time;
#synthesize score;
-(void)newGame{
int index=0;
int yAxis=70;
for(int y=0;y<100;y=y+10){
int xAxis=20;
for( int x = 1; x < 11; x++) {
buttonArray[index] = [[UIButton alloc]init];
buttonArray[index] = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[buttonArray[index] setTag:index];
[buttonArray[index] addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
buttonArray[index].frame = CGRectMake(xAxis, yAxis, 26.0, 26.0);
NSLog(#"tag:%d xAxis:%d yAxis:%d",buttonArray[index].tag,(int)buttonArray[index].frame.origin.x,(int)buttonArray[index].frame.origin.y);
[self.view addSubview:buttonArray[index]];
xAxis=xAxis+28;
index=x+y;
}
yAxis=yAxis+28;
}
//generate bombs
for (int bombs=0;bombs<10;bombs++){
bombArray[bombs]= (arc4random()%99);
//TODO compare against bombArray to make sure of no duplicates
NSLog(#"BOMB AT %d",bombArray[bombs]);
}
}
- (IBAction)resetPress:(id)sender {
[self newGame];
}
- (void)buttonClicked:(UIButton*)button
{
BOOL hit;
NSLog(#"SELECTED BUTTON:%d",button.tag);
for (int b=0;b<10;b++){
if (button.tag==bombArray[b]){
//BOMB HIT
hit=YES;
b=10;
}
else {
//no bomb
hit=NO;
}
}
if (hit==YES){
//if hit
NSLog(#"HIT AT %d",button.tag);
[self mineHit];
}
else {
//if not hit
NSLog(#"%d is clean",button.tag);
[self cleanHit:button];
}
}
-(void)mineHit{
for (int d=0;d<100;d++){
NSLog(#"%i",buttonArray[d].tag);
buttonArray[d].enabled=NO;
[buttonArray[d] setTitle:#"*" forState:UIControlStateDisabled];
}
}
-(void)cleanHit:(UIButton*)button{
button.enabled=NO;
[button setTitle:#"!" forState:UIControlStateDisabled];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self newGame];
}
- (void)viewDidUnload
{
[self setResetGame:nil];
[self setScoreLabel:nil];
[self setTimeLabel:nil];
[super viewDidUnload];
}
#end
Here is my .h file
#import <UIKit/UIKit.h>
NSInteger bombArray[];
UIButton *buttonArray[];
#interface basicsViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *resetGame;
#property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property int time;
#property int score;
-(void)newGame;
-(void)buttonClicked:(UIButton*)button;
-(void)mineHit;
-(void)cleanHit:(UIButton*)button;
#end
When I compile your code I get four warnings. All four warnings are the same and say:
Tentative array definition assumed to have one element
The warnings apply to the definition of your bombArray and buttonArray arrays in the interface (.h) file.
If we give the two arrays a size your -mineHit method works fine.
Change the beginning of your .h file to:
#import <UIKit/UIKit.h>
NSInteger bombArray[10];
UIButton *buttonArray[100];
#interface basicsViewController : UIViewController
The compiler generates warnings for a reason and it is a good idea to try and get your code to compile cleanly with no warnings or errors.
Update: While we are here there is no reason why you couldn't move these arrays inside the interface and declare them as instance variables. Doing this would mean that the arrays are associated with an individual instance of the view controller. It is unlikely that you'd have multiple instances of this view controller but it is better to do it correctly now than get bitten later.
#import <UIKit/UIKit.h>
#interface basicsViewController : UIViewController {
NSInteger bombArray[10];
UIButton *buttonArray[100];
}
Interestingly, moving the declaration into the interface turns the warnings into errors.