I have a UIScrollViewController that has a UIScrollView which holds an UIImageView inside.
In loadView of the the scroll view controller I download the image by calling a function, that's supposed to do the downloading on a different thread. I call this function from my loadView and then put the imageview inside the scrollview, and set the scrollview as the view of the controller.
The problem is I can't see the image when I run the program, after clicking a row in the tableView (which is supposed to push the scrollview with the image in it). However, if I change tabss (in the tabbarviewcontroller) and come back to this tab. The image will show.
So I think the image download happens, but I somehow have a problem showing it instantly on the screen. It only appears after I come back to it. What do I seem to be doing wrong? I'm new to threads so I suspect it's a problem with that. Also my code was working before I made it so that it would do the download in another thread, so I'm pretty sure it is related to that.
This is the function in the Photo.m which is an Entity in Core Data. This is supposed to do the download
- (void)processImageDataWithBlock:(void (^)(NSData *imageData))processImage {
NSString *url = self.imageURL;
dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Flickr download", NULL);
dispatch_async(downloadQueue, ^{
NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:url];
dispatch_async(callerQueue, ^{
processImage(imageData);
});
});
}
This is my loadView method in the PhotoScrollViewController.m
- (void)loadView {
[image processImageDataWithBlock:^(NSData *imageData) {
UIImage *imageToBeShown = [UIImage imageWithData:imageData];
imageView = [[UIImageView alloc] initWithImage:imageToBeShown];
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
scrollView = [[UIScrollView alloc] initWithFrame:applicationFrame];
scrollView.delegate = self;
scrollView.contentSize = imageToBeShown.size;
[scrollView addSubview:imageView];
self.title = image.title;
self.view = scrollView;
}];
}
Edited to add extra information that was added as an answer
Adding [super loadView] to my loadView method solved the issue. However, the documentation says that I shouldn't be calling super loadView from my loadView.
I tried moving the code to viewDidLoad and that works as well. But really this is the code that's setting the view so I feel like I should be putting it in loadView. But then it doesn't work when I use this multi threading mechanism for download.
Is this because the download is somehow interfering with me setting the view in loadView?
It seems loadView completes before the image download is done. You need some way of calling [imageView setNeedsDisplay] when image download is complete. You need some method that gets called when image download is done that accesses the imageView (perhaps using viewWithTag) from the scrollView and calls [imageView setNeedsDisplay];
Related
I want to simply have a loop so that an object continuously moves across the screen at the bottom. Here is my code it should be pretty easy to understand.
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(spawnRocket) withObject:self afterDelay:2]; //delay before the object moves
}
-(void)spawnRocket{
UIImageView *rocket=[[UIImageView alloc]initWithFrame:CGRectMake(-25, 528, 25, 40)]; //places imageview right off screen to the bottom left
rocket.backgroundColor=[UIColor grayColor];
[UIView animateWithDuration:5 animations:^(){rocket.frame=CGRectMake(345, 528, 25, 40);} completion:^(BOOL finished){if (finished)[self spawnRocket];}]; //this should hopefully make it so the object loops when it gets at the end of the screen
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
After doing all this i click run and all i see is a white screen on my iphone 6.0 simulator
ps. im running xcode 4.5.1
A few things:
UIImageView *rocket=[[UIImageView alloc]initWithFrame:...
You're not assigning an image to the image view, the best way to do this is to use:
UIImage* image = [UIImage imageNamed:#"image.png"];
UIImageView *rocket = [[UIImageView alloc] initWithImage:image];
rocket.frame = CGRectMake(-25, 528, 25, 40);
(The root cause of your problem) You are not adding your UIImageView to your main view, hence it's not being displayed. In spawnRocket, you should do:
[self.view addSubview:rocket];
Note: Because you want this to be done in a loop, you're gonna have to make sure your memory management is in order.
I don't know whether you still want the rocket on screen after it's finished moving, but if not, remember to keep a reference to the UIImageView and removeFromSuperview when you're done (to prevent memory leaks).
Calling spawnRocket in viewDidLoad is probably not the best idea, it may not reach the screen yet when spawnRocket is called. Try calling it in viewWillAppear or viewDidAppear (whatever is best in your case)
[self performSelector:#selector(spawnRocket) withObject:self afterDelay:2];
You don't need to provide self within withObject:, you're not accepting any parameters within spawnRocket
You don't add the UIImageView to any parent view. It will just live in memory, but not be displayed. Add it to your view controller's view after creating it:
[self.view addSubview:rocket];
I am developing a similar program like Photos in the iPhone using ALAssetLibrary. I am trying to load the images in a scrollview. Everything works fine when the album has small amount of pictures. But when I trying to load the album with 200+ photos, my program ended without any error message. Anyone know this program?
Here is my code for loading scroll view:
- (void)loadScrollView
{
for (UIView *v in [scrollview subviews]) {
[v removeFromSuperview];
}
CGRect scrollFrame = [self frameForPagingScrollView];
scrollview = [[UIScrollView alloc] initWithFrame:scrollFrame];
CGRect workingFrame = scrollview.frame;
workingFrame.origin.y = 0;
photoCount = [info count];
for(NSDictionary *dict in info) {
UIImageView *imageview = [[UIImageView alloc] initWithImage:[dict objectForKey:UIImagePickerControllerOriginalImage]];
[imageview setContentMode:UIViewContentModeScaleAspectFit];
imageview.frame = workingFrame;
[scrollview addSubview:imageview];
[imageview release];
[scrollview setPagingEnabled:YES];
[scrollview setDelegate:self];
[scrollview setAutoresizesSubviews:YES];
[scrollview setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[scrollview setShowsVerticalScrollIndicator:NO];
[scrollview setShowsHorizontalScrollIndicator:NO];
workingFrame.origin.x = workingFrame.origin.x + workingFrame.size.width;
}
[self setScrollViewContentSize];
[[self view] addSubview:scrollview];
}
Thanks a lot in advance!!!
I personally put all of my UIImageView objects on my UIScrollView, but only set the image property for them for those that are currently visible (and clear the image property for those that are no longer visible). If you have thousands of images, perhaps even that is too wasteful (perhaps you don't even want to keep the UIImageView objects, even without their image property set, around), but if you're dealing with hundreds, I find it is a nice easy solution, addressing the key problem of the memory consumed by the UIImage objects:
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.delegate = self;
// all of my other viewDidLoad stuff...
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self loadVisibleImages];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self loadVisibleImages];
}
- (void)loadVisibleImages
{
CGPoint contentOffset = self.scrollView.contentOffset;
CGRect contentFrame = self.scrollView.bounds;
contentFrame.origin = contentOffset;
for (UIImageView *imageView in _imageViews) // _imageViews is (obviously) my array of images
{
if (CGRectIntersectsRect(contentFrame, imageView.frame))
{
imageView.image = ... // set the image property
}
else
{
imageView.image = nil;
}
}
}
This is a snippet from some code that's doing a bunch of other stuff, so clearly your implementation will differ significantly, but it shows how you can use scrollViewDidScroll to determine what images are visible and load/unload images appropriately. You could probably alter further if you want to also remove/add the UIImageView objects, too, but clearly the logic of "is this imageview visible" would have to be changed, rather than leveraging the frame of all of the UIImageView objects.
I'm not sure if I'm reading your code right, but do you also have all of your UIImage objects sitting in a dictionary, too? That's pretty extravagant use of memory, itself. I usually keep the actual images in some persistent store (e.g. I use Documents folder, though you could use Core Data or SQLite, though the latter two impose a significant performance hit for large images). The only images I keep in memory are the ones actively used by the UI and I'll use a NSCache object to keep a few around for performance reasons, but I otherwise pull them from persistent storage, not active memory.
You should use scroll view delegation to determine which images would be showing on the screen at the current time, and only have those loaded in memory.
Also, if you are displaying a much smaller than the actual image size, you should resize the image and use the smaller image.
why not use UICollectionView and UICollectionViewController classes? UICollectionViewController reference here
sample code here.
Your images will end up being instances of UICollectionViewCell. The data source and delegate protocol methods are similar to the UITableView methods. i.e. they provide a mechanism where the UICollectionViewController will reuse the UICollectionViewCells.
The benefit of using these classes is that they are used similarly to UITableViewControllers and they assist you with the memory pressure issues that you are having.
good luck!
I have a Tableview containing cells with images, however the cells that are reused still contain the image from the previous cell that is being reused until the new image is downloaded and set.
I have tried setting the image to nil. (imageV is a subclass of HJManagedImageV, source can be found here: HJManagedImageV
[cell.imageV setImage:nil];
the setter
-(void)setImage:(UIImage *)theImage{
if (theImage==image) {
//when the same image is on the screen multiple times, an image that is alredy set might be set again with the same image.
return;
}
[theImage retain];
[image release];
image = theImage;
[imageView removeFromSuperview];
self.imageView = [[[UIImageView alloc] initWithImage:theImage] autorelease];
[self addSubview:imageView];
[imageView setNeedsLayout];
[self setNeedsLayout];
[loadingWheel stopAnimating];
[loadingWheel removeFromSuperview];
self.loadingWheel = nil;
self.hidden=NO;
if (image!=nil) {
[callbackOnSetImage managedImageSet:self];
}
}
I have a workaround for by setting imageV to hidden but then I lose the loading spinner and I'd really like to know why setting it to nil isn't working.
Anyone have any ideas, cause I'm all out of them. Or am I missing a step somewhere.
Are the cells a subclass of UITableViewCell? If yes, I would try to implement the method prepareForReuse and see if can solve the problem there.
Hope this helps =)
Don't set it to nil, nor hide it.
We use a similar class and what we do for such cases is to set a start image to be displayed until the new image loads.
So in your case you could simply set the image as a blank jpg/png inside your bundle instead of nil
I have an app where it reads an XML document containing 2 fields (image url and description). If the document has a field that has a valid image URL, I want the view to show the image. Else I just want it to show the description.
The problem I am having is :
how to show the UIImageView dynamically
how to move the UITextView downwards since now I added a UIImageView
Currently I just have a View with a UITextView on it.
Any ideas?
1) -[UIView setHidden:] if the UIImageView is already in place. Otherwise, [[UIImageView alloc] initWithFrame:], then add the image view as a subview to the view.
2) -[UIView setFrame:] (is one option)
The following is what you could do.
Prepare the the view with the UIImageView and the UITextView in the viewDidLoad like this:
-(void) viewDidLoad)
{
[super viewDidLoad];
myImageView = [[UIImageView alloc] init];
myImageView.frame = CGRectMake(x,y,0,0); //you can set it at the right position and even set either the width OR the height depending on where you want the textView in my example I'm gonna assume its beneath the imageView
//other imageView settings
[myView addSubView:myImageView];
myTextView = [[myTextView alloc] init];
myTextView.frame = CGRectMake(x,y,width,heigh+CGRectGetMaxY(myImageView.frame)); //here you prepare the textView for the imageView and the space it takes. But doesn't get hindered by it if it's not there.
//other textView settings.
[myView addSubView:myTextView];
//You would want this loading function about here OR after this loading has been occured.
[self loadImageFromURL];
}
-(void)loadImageFromURL
{
//get your image here.. or not
if(imageHasLoaded) //some condition that gets set when your image has successfully loaded. (This should be done in a delegate (didLoadImageFromURL) if you have such thing prepared.
{
//myImageView.image = theLoadedImage;
myImageView.frame = CGRectMake(x,y,theLoadedImageWidth,theLoadedImageHeight);
//after you set the frame of the imageView you need to refresh the view
[myView setNeedsDisplay];
}
}
And this is how you should dynamically add an imageView with image and frame to some view.
I need to make a snapshot or a screenshot - call it as you like- of the current view and then display it in the modal view.
Because if I just write this in the ModalView's View controller
MyAppViewController *viewController = [[MyAppViewController alloc] init];
self.view = viewController.view;
All the methods of the MyAppViewController are called as well, but I don't need it, I just need to "save" everything that was on the screen when the ModalView appeared and show it in the ModalView's view.
How can I do it?
Thanks in advance!
I would suggest doing this:
Have a method that creates an image out of the contents of the view.
-(UIImage*) makeImage {
UIGraphicsBeginImageContext(self.view.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
Create a custom init method for your modal view, and also give your modalView an instance variable that can hold a UIImage something like...
- (id)initWithImage:(UIImage *)temp {
myImage = temp;
}
Then in your modalView, perhaps in the viewDidLoad method, create a UIImageView and set the image to myImage
Hopefully this achieves what you are trying to do.
In iOS 7, you can use one of the new methods in UIView for creating snapshots, for instance:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates;
which returns a UIView which you can then add as a subview.
UIView *viewSnapShop = [self snapshotViewAfterScreenUpdates:NO];