Update UIImageView from a delegate - objective-c

I have a Question regarding the UIImageView.
I have a ObjectiveC-Class that contains a UIImageView and receive a callback from a delegate.
From the delegate I get the UIImage that I want to insert to my UIImageView. But when I set the image it won't be shown.
#interface MyViewController ()
#property(nonatomic,strong) UIImageView* myImageView;
#implementation MyViewController
-(void) viewDidLoad
{
[super viewDidLoad];
self.myImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0,0,400,400)];
[self.view addSubview:self.myImageView];
-(void)frameworkService:(FrameworkService *)frameworkService
callbackfromDelegate:(UIImage*) myImage
{
self.myImageView.image = myImage;
}
}

You can only update the UI from the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
... update here your ui ..
});

If the callback is not on the main thread, UI won't update. But dispatch_async is unnecessary, just call:
[self.myImageView performSelectorOnMainThread:setImage: withObject:myImage waitUntilDone:NO];
See https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/doc/uid/20000050-CJBEHAEF

Related

Objective C: Download Thread not affecting UI, even possible?

I'm working through the Standford course on ItunesU and there they say a thread to download something should not do anything to the UI, this should only happen on the Main Thread.
Well, in my example I'm downloading a picture from Flicker and I want to setup this picture (via a segue) in a UIScrollView. So while I'm downloading this picture in the "side" thread I'm setting the image property of the UIScrollview to image etc. However this doesn't work obviously, because I don't know the imagesize yet and I also don't have a reference to that image object yet to set right?
So how do you handle that? I hope I'm clear..here is my example:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(NSIndexPath *)sender{
NSDictionary *selectedPhoto = [self.photos objectAtIndex:sender.row];
[self.defaults addPhotoToRecentlyViewed:selectedPhoto];
[self.defaults saveDefaults];
PhotoViewer *photoViewer = segue.destinationViewController;
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
photoViewer.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:spinner];
[spinner startAnimating];
dispatch_queue_t photoDownload = dispatch_queue_create("photoviewever", nil);
dispatch_async(photoDownload, ^{
NSData *data = [NSData dataWithContentsOfURL:[FlickrFetcher urlForPhoto:selectedPhoto format:FlickrPhotoFormatLarge]];
UIImage *image = [UIImage imageWithData:data];
photoViewer.image = image;
dispatch_async(dispatch_get_main_queue(), ^{
photoViewer.title = [selectedPhoto objectForKey:FLICKR_PHOTO_TITLE];
photoViewer.navigationItem.rightBarButtonItem = nil;
});
});
}
and my PhotoViewer:
#import "PhotoViewer.h"
#interface PhotoViewer ()
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIImageView *imageView;
#end
#implementation PhotoViewer
#synthesize scrollView = _scrollView;
#synthesize imageView = _imageView;
#synthesize image = _image;
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.delegate = self;
[self setupImage];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return self.imageView;
}
- (void)setupImage{
self.imageView.image = self.image;
self.imageView.frame = CGRectMake(0, 0, self.image.size.width, self.image.size.height);
self.scrollView.contentSize = self.image.size;
[self.imageView setNeedsDisplay];
}
#end
You should move this line:
photoViewer.image = image;
into the "completion" block you dispatch back to the main thread. PhotoViewer appears to be a view controller, and as such it's subject to the same "main thread only" rules as other UI. Also, it looks like you'll need to call -setupImage again from that main thread "completion" block. (Otherwise the image will never get pushed into the image view, as you noted.)
Also in -setupImage, you want to check that self.image returns a non-nil value before addressing into it. Depending on which compiler you're using, the behavior of calling a struct-returning Objective C method is 'undefined' (i.e. self.image.size returns a CGSize struct). (Although in recent compilers, it returns a zero filled struct.)

UIGestureRecognizer - Get the reference to the touched UIViewController Instead of its View?

How do I get a reference to the UIViewController of a touched view?
I am using a UIPanGestureRecognizer on the view of a UIViewController. Here's how I initialize it:
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc]init];
[[self view]addSubview:[thisTaskController view]];
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[thisTaskController view] addGestureRecognizer:panRec];
In the tiggered action triggered using the gesture recognizer I am able to get the view from the parameter using recognizer.view
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
UIView *touchedView = [[UIView alloc]init];
touchedView = (UIView*)[recognizer view];
...
}
However what I really need is the underlying UIViewController of the view touched. How can I get a reference to the UIViewController that contains this view instead of only the UIView?
I would say that it is more a design issue than just getting a reference. So I would follow several simple advises:
Owner should catch events from its view. I.e. TaskUIViewController sould be a target to UIPanGestureRecognizer which you added to its view.
If a controller has a sub-controller and waits from its sub-controller some responses - implement this as delegate.
You have memory leak in your "handlePan:" method.
Here is a skeleton to solve your issue:
#protocol CallbackFromMySubcontroller <NSObject>
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController;
#end
#interface OwnerController : UIViewController <CallbackFromMySubcontroller>
#end
#implementation OwnerController
- (id)init
{
...
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc] init];
...
}
- (void)viewDidLoad
{
...
[self.view addSubview:thisTaskController.view];
...
}
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController
{
NSLog(#"Yahoo. I got an event from my subController's view");
}
#end
#interface TaskUIViewController : UIViewController {
id <CallbackFromMySubcontroller> delegate;
}
#end
#implementation TaskUIViewController
- (id)initWithOwner:(id<CallbackFromMySubcontroller>)owner
{
...
delegate = owner;
...
}
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer:panRec];
[panRec release];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
...
[delegate calbackFromTaskUIViewControllerOnPanGesture:self];
...
}
#end
[touchedView nextResponder] will return the UIViewController object that manages touchedView (if it has one) or touchedView's superview (if it doesn’t have a UIViewController object that manages it).
For more information, see the UIResponder Class Reference. (UIViewController and UIView are subclasses of UIResponder.)
In your case, since you happen to know that touchedView is your viewController's view (and not, for instance, a subview of your viewController's view), you can just use:
TaskUIViewController *touchedController = (TaskUIViewController *)[touchedView nextResponder];
In the more general case, you could work up the responder chain until you find an object of kind UIViewController:
id aNextResponder = [touchedView nextResponder];
while (aNextResponder != nil)
{
if ([aNextResponder isKindOfClass:[UIViewController class]])
{
// we have found the viewController that manages touchedView,
// so we break out of the while loop:
break;
}
else
{
// we have yet to find the managing viewController,
// so we examine the next responder in the responder chain
aNextResponder = [aNextResponder nextResponder];
}
}
// outside the while loop. at this point aNextResponder points to
// touchedView's managing viewController (or nil if it doesn't have one).
UIViewController *eureka = (UIViewController *)aNextResponder;

Immediately starting new UIViewController from a viewcontroller

When the UIViewController starts, I want to start another UIViewController immediately.
This doesn't work:
-(void) awakeFromNib {
UIViewController *newcontroller = [[[UIViewController alloc] init] autorelease];
...
[self presentModalViewController:newcontroller animated:YES];
}
In order for this to work, I have to do afterDelay for a method, like so:
-(void) awakeFromNib {
[self performSelector:#selector(startNewController) withObject:nil afterDelay:0.5];
[super init];
}
-(void) startNewController {
UIViewController *newcontroller = [[[UIViewController alloc] init] autorelease];
...
}
Is it possible to get it to work without delay?
Call startNewController in your viewDidAppear method instead, that happens because your viewController is not totally loaded when you try to present the modal viewController, so that's why it works when you wait.
Practically, you should not plan your application architecture which forces you to do such implementations. Though, I can understand there are times where you have no way out..
I'd say:
best solution for your case is to call your controller from
viewDidAppear
or
viewWillAppear

Calling SEL from parentViewController in iOS

I have a ViewController in which I create a Modal ViewController. I have a SEL value in my modal that I set when it is being instantiated from the parent.
setDateViewController.selectorName = #selector(myMethod:);
In my modal I am trying to call this SEL like:
[[self parentViewController] performSelector:self.selectorName withObject:selectedDate afterDelay:.5];
{selectedDate} is obviously a value from my modal.
I don't get any errors or stack, however, this SEL (method) on my parent is never being called. For some reason I think this should work, but something tells me I'm way off track.
Thanks.
I guess [self parentviewcontroller] is not returning anything.
Try UiviewController* v = [self parentviewcontroller]; and check if is nil. Most probably it should be nil. Else if its was pointing to another object of different class then it would have crashed. Please do one thing. YOu should set bot the object and the methd you need to call. IT will solve any issues if it has any.
setDateViewController.selectorDelegate = self;
setDateViewController.selectorName = #selector(myMethod:);
call like this from parent class. So you can dynamically specify the method and the object you want to call gives more flexibility.
and use,
[selectorDelegate performSelector:self.selectorName withObject:selectedDate afterDelay:.5];
this should solve any issues.
Perhaps you would consider added a delegate protocol to your modal that will allow it to call the method on the parent.
Quick (untested) example:
// MyController.h
#protocol MyControllerDelegate;
#interface MyController : UIViewController
{
id<MyControllerDelegate> delegate;
}
#end
#protocol MyControllerDelegate <NSObject>
- (void)methodToCall:(id)sender;
#end
// MyControler.m
#implementation MyController
- (void) loadView
{
[super loadView];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(20.0f, 20.0f, 50.0f, 30.0f);
[btn setTitle:#"blah" forState:UIControlStateNormal];
[btn addTarget:self.delegate
action:#selector(methodToCall:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
#end
// ParentController.h
#interface ParentController : UIViewController<MyControllerDelegate>
{
}
#end
// ParentController.m
#implementation ParentController
- (void)methodToCall:(id)sender
{
NSLog(#"HERE");
}
#end
Just make sure when you are creating your modal controller you set it's delegate to self on the parent:
MyController *controller = [[MyController alloc] init];
controller.delegate = self;
[self presentModalViewController:controller animated:YES];

Create UIImageView in object

I am trying to create a UIImageView programmatically in a method of a object.
-(void) createApple
{
CGRect myImageRect = CGRectMake(100, 100, 64, 64);
image = [[UIImageView alloc] initWithFrame:myImageRect];
[image setImage:[UIImage imageNamed:#"Apple.png"]];
[self.view addSubview:image];
[image release];
}
when I use this code in the ViewController.m it works fine but when I use it in my Apple object I get an error:request for member 'view' in something not a structure or union.
What do I use instead of self.view???
Sorry if I'm repeating posts. I saw a similar question to this but wasn't sure if it was exactly the same situation.
If I got your question correctly you have an interface like this
#interface Apple: NSObject
#end
If you want to create an UIImageView using NSObject methods try this
//first declare a method of UIImageView type
#interface Apple: NSObject
{
}
-(UIImageView *)ImageView;
#end
now come in your Apple.m file
#implementation Apple
-(UIImageView *)ImageView
{
UIImageView *imgView = [UIImageView alloc]initWithFrame:CGRectMake(100, 100, 64, 64)];
imgView setImage:[UIImage imageNamed:#"Apple.png"];
return imgView;
}
#end
now call this method where you want to show your image, in your ViewController.m file you can call it like this- You just have to import your object class first.
#import "ViewController.h"
#import "Apple.h"
#implementation ViewController
-(Void)ViewDidLoad
{
Apple *appleObject = [Apple alloc]init];
UIImageView *imageViewOfViewController = [appleObject ImageView];
[self.view addSubView:imageViewOfViewController];
}
#end
I show you imageview method in viewDidLoad method you can call it any where like this.
The line causing the error would be:
[self.view addSubview:image];
If your Apple object does not extend UIViewController then there will be so self.view property on it, unless you have defined one yourself.
As to what to use instead, that is difficult to say without knowing specifically what you are trying to do.
However, assuming that you want your Apple class to display something on the screen, then probably what you want to do is update Apple so that it extends either UIViewController or (perhaps) UIView. Then in the first case you can use self.view, and in the second case you can just do [self addSubview: image];.
Because "view" is a property of UIViewController and your "apple object" doesn’t have one.
The problem is, you're not calling this method inside UIViewController. Personally I'd do it slightly other way:
- (UIView*) newAppleView
{
CGRect myImageRect = CGRectMake(100, 100, 64, 64);
UIImageView *imageView;
imageView = [[UIImageView alloc] initWithFrame:myImageRect];
[imageView setImage:[UIImage imageNamed:#"Apple.png"]];
return [imageView autorelease];
}
Assuming the newAppleView method is implemented in your UIViewController, you can easly add new apple:
[self.view addSubview:[self newAppleView]];