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

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.)

Related

Update UIImageView from a delegate

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

UIViewController Retaining in ARC

I have a subclass of UIViewController -> MyPopUpViewController
#protocol MyPopUpViewController Delegate;
#interface MyPopUpViewController : UIViewController
{
}
#property (nonatomic, strong) id <MyPopUpViewControllerDelegate> delegate;
-(IBAction) buttonPressed:(id)sender;
#end
#protocol MyPopUpViewControllerDelegate
-(void) popupButtonPressed: (MyPopUpViewController*)controller;
#end
I cannot have this MyPopUpViewController as an instance variable because this comes externally, and there could be many and multiple of these popups can be up. So far I tried this, and it crashes on the delegate call due to not being retained:
MyMainViewController:
-(void)externalNotificationReceived: (NSString*) sentMessage
{
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[self.view addSubview:popupView.view];
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
}
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
NSLog(#"Popup Delegate Called");
[controller.view removeFromSuperview];
controller.delegate = nil;
controller = nil;
}
Once the popup comes up, and when the ok button is tapped, it crashes and never gets to that NSLog. How can I change
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
..so it would retain without making it an instance variable?
Thanks in advance.
You should be doing proper view controller containment by calling addChildViewController:.
- (void)externalNotificationReceived: (NSString*) sentMessage {
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
[self addChildViewController:popupView];
[self.view addSubview:popupView.view];
[popupView didMoveToParentViewController:self];
}
This will keep a proper reference to the view controller as well as properly pass various view controller events. Read about this in the docs for UIViewController and the "View Controller Programming Guide for iOS".
BTW - you should name your methods better. Example:
popupButtonPressed::
should be named:
popupButtonPressed:buttonNumber:
Usually delegates are weak-referenced instead of strong. I, myself, would name it something else as to not confuse other people.
Also, the following bit of code will have no effect:
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
...
controller = nil;
}
the controller would be released (set to nil) automatically at the end of the scope.

add GLKViewController to subview - GLKView context causes crash

I have a pretty simple set up in mind, having a mainViewController that has a GLKViewController on top of it. The idea is having my GLKViewController in a box, that take sup 1/3 of the screen, on the mainViewController. This can be seen below:
That white box is my own custom GLKViewController with the follow code:
boxViewController.h
//boxViewController.h
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#interface boxViewController : GLKViewController
#end
boxViewController.m
//boxViewController.m
#import "boxViewController.m"
#interface boxViewController () { }
#property (strong, nonatomic) EAGLContext *context;
#end
#implementation boxViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.context) {
NSLog(#"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
// view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
}
#end
On my mainViewController in the viewDidLoad I simply call boxViewController like this:
boxViewController* box = [[boxChartViewController alloc] init];
box.view.layer.frame = CGRectMake(10, 50, self.view.frame.size.width-20, self.view.frame.size.height/3);
[self.view addSubview:box.view];
which works perfect.
Notice that in my boxViewController.m I had view.context = self.context commented out. If you uncomment it, my application crashes without any error messaging (it breaks with a EXC_BAD_ACCESS in the objc_msgSend assembly code [line 8 to be specific]).
What am I doing incorrectly that when I set the context the application crashes? From all the tutorials I noticed that they have the same set up, except not setting the controller on another controller. Though I don't understand why GLKViewController couldn't be framed on another controller, so I don't think that's the issue.
After a few hours of messing around I found that adding the viewController as a child works:
#import "mainViewController.h"
#implementation mainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.layer.backgroundColor = [UIColor colorWithRed:242.0f/255.0f green:242.0f/255.0f blue:242.0f/255.0f alpha:1.0].CGColor;
boxViewController* chart = [[boxViewController alloc] init];
chart.view.layer.frame = CGRectMake(10, 50, self.view.frame.size.width-20, self.view.frame.size.height/3);
chart.view.layer.borderColor = [UIColor blackColor].CGColor;
chart.view.layer.borderWidth = 2.0f;
[self addChildViewController:chart];
[self.view addSubview:chart.view];
}

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.

Changing an image in MKMapKit MKAnnotation Subclass

I am having some problems with changing an image view inside of an MKAnnotationView subclass. I have a timer that updates the annotation position based on data about where a user was at a certain point. This works fine and the annotation position is updated correctly. I do this with the following code:
[_myAnnotation setCoordinate:point.position];
Below this, I also calculate whether the user was heading east or west. If the user is heading east, I call the setIsMovingRight:YES method on the MyAnnotation class, and do the opposite for when the user is heading west.
I have verified that the method is being called with the correct values at the correct times by using NSLog inside the method to print out whenever the value changes. However, it doesn't seem like the setIsMovingRight method has any effect on the visual appearance of the annotation. I try setting the background colour of the imageview to red whenever the method is called, but even this has no effect. I have tried calling setNeedsDisplay in various places with no success.
I can add and recreate an annotation view, but then the annotation flashes on and off whenever the position changes and it really does not look very good.
The following is the code for my custom annotation view, "MyAnnotation".
#interface MyAnnotation : MKAnnotationView <MKAnnotation>
{
UIImageView *_imageView;
BOOL _currentlyMovingRight;
}
#property (nonatomic) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
#property (nonatomic,retain) UIImage *image;
-(void)setIsMovingRight:(BOOL)movingRight;
#end
#implementation MyAnnotation
#synthesize coordinate, title, subtitle, image;
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if(self)
{
_currentlyMovingRight = NO;
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"right.png"]];
[self addSubview:_imageView];
}
return self;
}
-(void)setIsMovingRight:(BOOL)movingRight
{
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[_imageView setBackgroundColor:[UIColor redColor]]; // THIS LINE NEVER DOES ANYTHING.
if(movingRight && !_currentlyMovingRight)
{
NSLog(#"right now.");
[_imageView setImage:[UIImage imageNamed:#"right.png"]];
}
else if (!movingRight && _currentlyMovingRight)
{
NSLog(#"left now.");
[_imageView setImage:[UIImage imageNamed:#"left.png"]];
}
_currentlyMovingRight = movingRight;
[self addSubview:_imageView];
[self setNeedsDisplay];
}
I would appreciate any help or advice that anyone could give. Thanks!
I think that the annotation views use KVO to listen for changes. Have you tried changing the image property? Something like [self setImage:myNewImage]