UIImageView setImage not working in a delegate method? - objective-c

I have a problem calling the setImage function in the opencv delegate method processImage.
When I call setImage in viewDidLoad, I can see the image, but when I do the same in processImage, it doesn't work.
What's the problem here?
- (void)viewDidLoad
{
[super viewDidLoad];
// This works !
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
}
- (void)processImage:(cv::Mat&)img {
// This does not work anymore !
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
}

When you modify the UI you must do it from the main thread, chances are that the delegate method, if it's being called, is called on another thread. Try this.
- (void)processImage:(cv::Mat&)img {
dispatch_async(dispatch_get_main_queue(), ^{
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
// I also think you should use the dot syntax, but that's purely a style thing
// processImageView.image = [UIImage imageNamed:#"resistor3.jpg"];
});
}
EDIT: Add recommendation about using dot syntax

Related

NSButton wont setFont: when layer backed

I have my NSButton layer backed because I wanted to use a custom image, but this seems like it's inhibiting the use of the setFont: method when I need to programmatically change the font, as when I comment out the code for wantsUpdateLayer: and updateLayer:, setFont: works, but when the layer methods are in the code, it does nothing.
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.fontChangeButton = [[CustomButton alloc]initWithFrame:NSMakeRect(82, 60, 190, 113)];
[self.window.contentView addSubview:self.fontChangeButton];
}
- (IBAction)changeFont:(id)sender {
[self.fontChangeButton fontChange];
}
#end
#implementation CustomButton
- (void)fontChange{
[self setFont:[NSFont fontWithName:#"Dosis Bold" size:40]];
}
//when these are commented out, setFont: works, but I need them in for the custom button images
- (BOOL)wantsUpdateLayer{
return YES;
}
- (void)updateLayer{
if (self.state == NSOnState) {
self.layer.contents = [NSImage imageNamed:#"buttonPressed.png"];
}else
self.layer.contents = [NSImage imageNamed:#"buttonUnpressed.png"];
}
This thread offers a workaround, but I'd much rather understand why this is happening and fix it: Can't Change NSButton Font
By overriding -wantsUpdateLayer to return YES you're bypassing calls to -drawRect:. This facility was introduced in 10.8 and exists for efficiency purposes.
There are two things I think should be clarified:
1 - You don't need to override -wantsUpdateLayer to be layer-backed. Just send -setWantsLayer:YES to your button to be layer-backed.
2 - In your example, creating a custom NSButtonCell class might be a better approach to what you're trying to do. Have a look at Apple's documentation on subclassing NSControl and this how to to get started.

Animation is not being performed through to another view controller

I am trying to trigger an animation in one view, based on what is happening in a separate class file. It gets to the method, supported by the fact that it does spit out the two NSLog statements, but doesn't commit the UIView Animation
Here is the code:
ViewController.h
#interface ViewController : UIViewController {
}
-(void)closeMenu;
ViewController.m
-(void)closeMenu{
//close the menu
NSLog(#"Got here");
[UIView animateWithDuration:0.6 animations:^{
[UIView setAnimationBeginsFromCurrentState:YES]; // so it doesn't cut randomly, begins from where it is
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}];
NSLog(#"Got here2");
}
OtherClass.m (commented code may be irrelevant to this question, of course still used in actual application. Jut thought it might make it easier for comprehension)
#import "ViewController.h"
...
//- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
//{
ViewController *foo = [[[ViewController alloc] init] autorelease];
//SelectableCellState state = subItem.selectableCellState;
//NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
//switch (state) {
//case Checked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[foo closeMenu];
//break;
//case Unchecked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
//break;
//default:
//break;
//}
}
You're mixing old fashioned animation, with block based animation. For example, in the documentation, it states for setAnimationBeginsFromCurrentState:
Use of this method is discouraged in iOS 4.0 and later. Instead, you
should use theanimateWithDuration:delay:options:animations:completion:
method to specify your animations and the animation options.
I'm not 100% sure if this is supported. You should change your animation code to this at least:
[UIView animateWithDuration:0.6
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
animations:^{
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}
completion:nil];
Apart from that, it seems like it should work. It may cause issues if anything else if affecting the frame. It may be worth calculating the frame before the animation block. A'la:
CGRect newFrame = CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)
[UIView animateWithDuration...:^{ menuView.frame = newFrame; }...];
EDIT: Oh wait, looks like you're alloc/init'ing the object in (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem, and calling the method on it, but the view is nowhere in the view hierarchy. You need to call the animation on an instance which is being displayed on screen. Hope that makes sense?
EDIT 2: To call it on an instance which is already being displayed, typically you need to store it in an instance variable. I can't say exactly how in your situation, but generally it'd be of the form:
#interface OtherClass () {
ViewController* m_viewController;
}
#end
....
- (void)viewDidLoad // Or where ever you first create your view controller
{
...
// If you're adding a ViewController within another ViewController, you probably need View Controller Containment
m_viewController = [[ViewController alloc] init];
[self addChildViewController:m_viewController];
[m_viewController didMoveToParentViewController:self];
[self.view addSubview:m_viewController.view];
...
}
// If you're using ARC code, the dealloc wouldn't typically be necessary
- (void)dealloc
{
[m_viewController release];
[super dealloc];
}
//- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
//{
//SelectableCellState state = subItem.selectableCellState;
//NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
//switch (state) {
//case Checked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[m_viewController closeMenu];
//break;
//case Unchecked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
//break;
//default:
//break;
//}
}
If you need to access it from outside the class, this won't be sufficient, use properties for that. I.e.
Header File
#property (nonatomic, strong) ViewController* myViewController
.m file
// Use it as such
[self.myViewController closeMenu];
That animation code is really strange. You are mixing the new and the old UIView animation code and I don't think you can do that (but I could be wrong).
Since your have begun using the block based API I would recommend going that route (Apple recommends the same thing).
There is a similar method to the one you've used that takes options called animateWithDuration:delay:options:animations:completion:. You can pass in 0 for the delay and an empty block that takes a BOOL for the completion.
The two flags you want to pass for the options are UIViewAnimationOptionBeginFromCurrentState and UIViewAnimationOptionCurveEaseInOut.
Your code would look something like this
[UIView animateWithDuration:0.6
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
animations:^{
// set your frame here...
[menuView setFrame:CGRectMake(menuView.frame.origin.x,
-menuView.frame.size.height,
menuView.frame.size.width,
menuView.frame.size.height)];
} completion:^(BOOL finished){}];

Grand Central Dispatch. When using GCD [spinner startAnimating] similar to [myView setNeedsDisplay]?

In Grand Central Dispatch I want to start a spinner - UIActivityIndicatorView - spinning prior to beginning long running task:
dispatch_async(cloudQueue, ^{
dispatch_async(dispatch_get_main_queue(),
^{
[self spinnerSpin:YES];
});
[self performLongRunningTask];
dispatch_async(dispatch_get_main_queue(),
^{
[self spinnerSpin:NO];
});
});
Here is the spinnerSpin method:
- (void)spinnerSpin:(BOOL)spin {
ALog(#"spinner %#", (YES == spin) ? #"spin" : #"stop");
if (spin == [self.spinner isAnimating]) return;
if (YES == spin) {
self.hidden = NO;
[self.spinner startAnimating];
} else {
[self.spinner stopAnimating];
self.hidden = YES;
}
}
One thing I have never seen discussed is the difference - if any - between [myView setNeedsDisplay] and [myActivityIndicatorView startAnimating]. Do they behave the same?
Thanks,
Doug
The [UIView setNeedsDisplay] method has nothing to do with a UIActivityIndicatorView's animation state.
setNeedsDisplay simply informs the system that this view's state has changed in a way that invalidates its currently drawn representation. In other words, it asks the system to invoke that view's drawRect method on the next drawing cycle.
You very rarely need to invoke setNeedsDisplay from outside of a view, from code that is consuming the view. This method is meant to be invoked by the view's internal logic code, whenever something changes in its internal state that requires a redraw of the view.
The [UIActivityIndicatorView startAnimating] method is specific to the UIActivityIndicatorView class and simply asks the indicator to start animating (e.g. spinning). This method is instant, without requiring you to call any other method.
On a side note, you could simplify your code by simply calling startAnimating or stopAnimating without manually showing/hiding it. The UIActivityIndicatorView class has a hidesWhenStopped boolean property that defaults to YES, which means that the spinner will show itself as soon as it starts animating, and hide itself when it stops animating.
So your spinnerSpin: method could be refactored like this (as long as you haven't set the hidesWhenStopped property to NO):
- (void)spinnerSpin:(BOOL)spin {
if (YES == spin) {
[self.spinner startAnimating];
} else {
[self.spinner stopAnimating];
}
}

How to alloc initWithCoder or initWithFrame

#import "DotAnimation.h"
#implementation DotAnimation
DoodlePad* dp;
-(void)setupView {
Ball = CGRectMake(10, 10, 10, 10);
[NSTimer scheduledTimerWithTimeInterval:(1.0f / 30.0f) target:self selector:#selector(traceBall) userInfo:nil repeats:YES];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self == [super initWithCoder:aDecoder]){
[self setupView];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupView];
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.*/
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [[UIColor yellowColor] CGColor]);
CGContextFillEllipseInRect(context, Ball);
}
-(void)traceBall{
vector<CGPoint>::iterator L = dp.doodlePoints->begin();
while(L != dp.doodlePoints->end()){
Ball.origin.x += (*L).x;
Ball.origin.y += (*L).y;
++L;
}
[self setNeedsDisplay];
}
- (void)dealloc
{
[super dealloc];
}
#end
I have this Animation I am trying to use in another file.
So i figure i do something like
trace = [[DotAnimation alloc]initWithCoder];
or
trace = [[DotAnimation alloc]initWithFrame];
I am unsure which one to use or if i am even writing this correctly.
I want to be able to use:
while(k != dp.doodlePoints->end()){
Ball.origin.x += (*k).x;
Ball.origin.y += (*k).y;
++k;
}
In another file but I don't know how to call Ball.origin from DotAnimation
Also it would be great if you could link me to good information on understanding this.
initWithCoder is called by the framework when you embed an object into a XIB. Like a ViewController for example. So you should not call it by yourself.
Calling initWithFrame could be a better solution if you want to set the frame (like that seems to be done here). But you may give a frame, and add a subview created by yourself.
if you don't want to create the subview by yourself, then it's initWithNibName:bundle: that you may call/override. Creating the XIB that contains the view. But you will still need to add that view as subview to the main view that is showing that animated view.
initWithCoder: is something you would call yourself when restoring from an archive; it isn't appropriate for something which appears to be a view object. As it is only used for simple setup code, you can remove it.
Also, animating by changing the "Ball" object from another object isn't really consistent with Cocoa view programming. You should take a look at "Introduction to View Programming Guide for Cocoa" in the documentation, and work from there.

oddly a uibuttons text label won't change

This is very strange. My view is receiving a notification with an index number that I use to find a UIbutton by tag and then change its text label. The only trouble is the text will not change. I've put in break points and the code is being executed.
Is there some peculiarity with variable scopes when using notifications?
- (void)receiveEvent:(NSNotification *)notification {
int pass = [[[notification userInfo] valueForKey:#"index"] intValue];
NSLog(#"button index is %i", pass);
UIButton *changebut = (UIButton *) [self.view viewWithTag:pass];
ButtonStatusModel *m= [self.buttoneventpendingarray objectAtIndex:pass];
NSLog(#"current but status = %i",m.btnstatus);
if (m.btnstatus==3 ) {
//this should change the label but it does not;
[changebut setTitle:#"playing" forState:UIControlStateNormal];
m.btnstatus=2;
NSLog(#"should set text to 'playing");
//[engine addActionToQue:buttonid actionSample:actionsample action:1];
}else if (m.btnstatus==1) {
//this should change the label but it does not;
[changebut setTitle:#"idle" forState:UIControlStateNormal];
NSLog(#"should set text to 'idle");
m.btnstatus=0;
//[engine addActionToQue:buttonid actionSample:actionsample action:0];
}
}
EDIT 1
Thanks to commenters I've determined that even though my reciveevent function in on my main view controller the function is not being executed from the main thread (dont really understand this) and so thats why the button label will not change.
I used the following code to determine if it was the main thread or not. I think now to change the button label I need to call perform selector on main thread? Is this the correct thing to do? If so I need to pass a function 2 variables, I dont see how perform selector on main thread accomodates this.
-(void) updatebutton:(int)tag changeto:(int) val
{
if ([NSThread isMainThread]) {
{
NSLog(#"is main thread");
}
} else {
NSLog(#"is not thread");
[self performSelectorOnMainThread:#selector( /*function with 2 parameters here*/) withObject:nil waitUntilDone:YES];
}
}
EDIT 3*
Using blocks instead of main selector helped me out of my problem.
I really don't understand the whole main thread though. I assumed that if code was in my view controller that any executed code would be performed on the main thread. That seems to be big fat wrong. I would appreciate a little enlightenment on the matter.
Heres what I used anyway
-(void) updatebutton:(int)tag changeto:(int) val
{
UIButton *changebut = (UIButton *) [self.view viewWithTag:tag];
if ([NSThread isMainThread]) {
{
NSLog(#"is main thread");
if (val==2) {
[changebut setTitle:#"playing" forState:UIControlStateNormal];
}
if (val==0) {
[changebut setTitle:#"idle" forState:UIControlStateNormal];
}
}
} else {
NSLog(#"is not thread");
//[self performSelectorOnMainThread:#selector(updatebutton::) withObject:nil waitUntilDone:YES];
if (val==2) {
dispatch_async(dispatch_get_main_queue(), ^{
[changebut setTitle:#"playing" forState:UIControlStateNormal];
});
}
if (val==0) {
dispatch_async(dispatch_get_main_queue(), ^{
[changebut setTitle:#"idle" forState:UIControlStateNormal];
});
}
}
}
Try putting
[changebut setTitle:#"idle" forState:UIControlStateNormal];
in the -viewDidAppear method. If it doesn't work either, make sure that all the outlets are connected in IB. Moreover, try adding a few spaces as the button title in your .xib file (if you have one).
Is -receiveEvent called on another than the main thread? UI changes must be performed on the main thread.