I'm working on my first more complex app and couldn't find a solution to this one. I have loaded about 64 icons into my app. For a table view, a user can assign to each cell one of these icons. Basically when he edits the cell he gets to a new UIView with all the 64 icons. The currently chosen icon should have a border and if he clicks a different one, the border should move and the icon assigned to that selected item in the tableview.
My problems are now:
a) How do I load these 64 icons into my view? I've created the view with all the UIImageViews, but how do I load them into these imageviews? Where are they saved if I copy them just to my directory and how do I access them?
b) Is there an easier way than placing 64 different UIViews into this view manually and linking them up with IBOutles?
Your problem could easily be resolved using UICollectionView . A UICollectionView just acts like a UITableView .
Go through the link in order to find more about UICollection
http://skeuo.com/uicollectionview-custom-layout-tutorial
Using UICollection view you just need the pass the image object saved in your Array
That's a lot of questions on a wide variety of topics.
How do I load icons/images?
UIImage *image = [UIImage imageNamed:#"fubar.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
If you just copy images in your project they are part of the main bundle and for the most part you and pretty much ignore where they are saved. It's pretty easy to access them. (see above)
Is there an "easier way"... well, you'd have to give us a way if you want to know if there is an easier way. However I think a simple naming convention and a loop would be workable.
NSMutableArray *icons = [NSMutableArray array];
NSString *iconName;
UIImage *image;
UIImageView *imageView; = [[UIImageView alloc] initWithImage:image];
for (int count=0 ; count < 65 ; count++) {
iconName = [NSString stringWithFormat:#"icon%d.png", count];
image = [UIImage imageNamed:iconName];
[icons addObject:image];
}
Then you would use your icons array as the basis of your UITableView. About that though. You might want to look into using a UICollectionView instead. It's like a table, but it's a grid of items instead of just rows. It would lend itself better to a large set of images.
As for how you copy the images into the views, both your UITableView and your UICollectionView have methods where they "ask" for the data and give you a position. Based on the position information you just set the it asks for and it will handle the rest. It might look something like
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
// ...
// remembering 'icons' is our array of image data
cell.imageView.image = icons[indexPath.row];
// ...
}
Edit from Comments: How to tell if a file exists in the app bundle? (my answer is just a cut/paste)
[[NSFileManager defaultManager] fileExistsAtPath:pathAndFileName];
You would have to do this in code. I would suggest you put all your filenames in NSArray, then iterate through it and with each step create a UIImageView on your ViewController programmatically. I suggest though that you subclass UIButton so it is "selected" when you tap it and you can then put any image into it.
you can use gridView a open source component here is the link
GridView
Also to detect you can assign tag to each imageView like from 0 to 63 then when user choose 0th image you can assign that tag to a local variable so that next time when user views grid you can identify that user has previously chose 0th index image. I hope it helps you.
Related
So I'm new Objective C and I'm building my first app, although I'm stuck on a particular function for my character creation feature.
I have added 'back' & 'forward' buttons and these are meant to move between the selections that I have (e.g. shirt images).
I have 4 different choices for the shirt and was wondering how I would add these 4 images into an array that I would then be able to use with the 'back' and 'forward' buttons in order to navigate between the choices.
Any help would be appreciated.
Are you using Swift or Objective-C? I'll assume Objective-C. Ok. So you got it. Now you just got to implement it. You'll have to initialize a new array with the four images, arrayWithObjects: or initWithObjects:. You'll have to have a UIImageView drawn on the screen, added as a subview (UIView's addSubview:). Then you may want to keep a disposable "counter" private ivar (could be a property too if you wanted) that keeps track of the index of the current image being displayed (declared in your .m, .h is fine too). You could do this or you could have a dynamic getter method that returns the index based on the method objectAtIndex: on your array with your image view's image. When the user clicks forward or back the counter is updated (if it's an ivar and not a getter) and the image view's image is updated based on the new counter value (eg. self.imageView.image = imageArray[counter]) or something like that. You could disable the forward/back buttons if the counter value is on the edge of the array's count property. When the view is initially loaded you could load the image view's image based on the current counter value which would be 0 by default.
In this answer, I did not write the code for you. I showed you snippets of stuff and gave you different potential alternatives to write your program. You'll have to put it together yourself and choose what you're most comfortable with.
In Objective-C, there are two classes of objects: Primitives (like NSInteger, int, char) and everything else. Non primitives can be stored in NSArrays. NSArrays are immutable, meaning that you cannot change them once they have been created. However, there is a sub-class of NSArrays called NSMutableArray. You can add and delete objects to these and sort them if you want. So if you have four
UIImage objects, you could do something like this:
NSMutableArray* images = [NSMutableArray new];
[images addObject:image1];
[images addObject:image2];
[images addObject:image3];
[images addObject:image4];
....
The objects are added in order, so you can retrieve the second image (for example) like this:
UIImage* second = [images objectAtIndex:1];
(Indexing is zero-based)
I have a collectionView with UICollectionViewFlowLayout with some reusability:
- (CategoryCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CategoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CategoryCell" forIndexPath:indexPath];
[cell.actIn startAnimating];
cell.cellElementRepresentation = [self.localCategoriesObjects objectAtIndex:indexPath.row];
[cell tryToShow]; //this is a important method to my problem
return cell;
}
I commented for you tryToShow method. Becouse i think this is a problem. When i scroll to the not visible element in the list I could see "old" elemnts first then new element. Why is that? My tryToShow method is basically download manager. I try to describe it in comment. Here is the code:
-(void)tryToShow {
NSString* imageFile = self.linkToImageFileSavedOnDisk //This is my local file saved in documentsDirectory.
if([[NSFileManager defaultManager] fileExistsAtPath:imageFile]) {
[self.overlayButton setBackgroundImage:[UIImage imageWithContentsOfFile:imageFile] forState:UIControlStateNormal];
[self disableActIn]; //Here i stop and remove activity indicator.
} else {
// Here i fire NSURLConnection and try to download. When NSURLConnection finished i put image to overlayButton.
}
}
Here is delegate method:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Here do some stuff to convert Data into UIImage... not important.
[self.overlayButton setBackgroundImage:object forState:UIControlStateNormal];
}
The overlayButton is a button on the UICollectionViewCell. I have a custom class for it.
I can't figure it out. How it's possible to image from indexPath.row (for example 1) is visible after scroll ? The image is replaced by new one right after new is successfully download, but this is to late.
As the OP mention, the problem seems to be that you are setting the backgroundImage too late. So I would try this modifications:
First, clear your cell before setting the new values. Since you are reusing the cell, the old values (text and images) will still be present in the cell subviews. Setting those values to nil will clear your cell.
Implement a mechanism to validate or invalidate the result from the image request, since could get the result too late, maybe is no longer needed. Worst, you can set an image no longer correspondent with your data. You should check, before setting a downloaded image, that this image is consistent with the current shown data.
Wait a little before start the image request If you have a large number of cells, you should think that when the user quickly scroll the collection, you can start lots of requests for images that may never be displayed. I would avoid this behavior by waiting a little before start a new request, and checking if the cell has been reused again.
Use the NSURLRequest and configure the cachePolicy attribute as NSURLRequestReturnCacheDataElseLoad. This will store the responses for the images, there is no need to save the images explicitly save the image and check it's existence.
I think you need to set the background image of self.overlayButton to nil in the else clause where you start the web request.
[self.overlayButton setBackgroundImage:nil forState:UIControlStateNormal];
That way the cell won't appear to have an image loaded already when the user scrolls to it. Or, if it's not loading quickly enough (which it should), you could possibly set the background image to nil in the collectionView:didEndDisplayingCell:forItemAtIndexPath: delegate method instead.
But a problem I see is that you will be creating duplicate NSURLConnection objects every time tryToShow is called, i.e., the user scrolls down then revisits the cells at the top.
As #lucaslt89 suggested, you could create a dictionary of cells instead of using the default dequeuing. If you do it this way, make sure you have a property for the URL connection object on the cell, and you check for it before starting a new one.
Or, you may want to create a dictionary of any NSURLConnection objects you create in the view controller, and move the web request code into the view controller. If a web request is needed, the view controller would check if there was already a web request running in the dictionary first. When the request is finished, the request object would be removed from the dictionary. This would be the desirable method because the cell isn't responsible for creating its own data. In UICollectionView the cells have to quickly change roles and it's not wise to associate a particular cell with some data unless there are really few cells.
Either way, you'll avoid creating multiple URL requests for the same image.
Hope this helps!
I've been trying to figure out UIImage for some time now. I've been trying to figure out an approach to having one view the 'Main Game View' and showing either 2/3/4 different images depending on the 'level' variable. I'm just trying to be sure of logic. So for example level 1 would display 4 pictures and level 2 might display 3 different pictures. I don't want to hinder performance of the app but because the game is to be played offline and archiving won't make much of a difference all the images (several hundred optimised images) are being stored locally in the main app bundle.
I'm just wondering if my logic for trying to implement this so far is sound or not. For level 1 I would implement the 4 UIImageViews needed and initialise them with images, then display them on screen at set positions. I would then preload the next levels images using GCD. When a continue button is pressed I will set the UIImages and the UIImageViews to nil and display level 2's (or the next level) on screen.
I'm not confident in my approach and was wondering if there was something that would make it simpler or something I've missed or even if in practice it will work accordingly to the theory.
Thank you in advance for you time and any help.
Sorry if this is unclear.
I'm assuming you use a single view controller class for all your levels and load the levels from some .plist file or other format.
Don't bother using GCD. Loading 4 images once per level during loading costs practically nothing.
If all you need to do is have 2-4 images on the screen for each level (in addition to any other level elements), simply create 4 UIImageView instances and add them to your view. Then when you load a level, create new UIImage objects from the required image files and set your UIImageViews' image property to them. The UIImage objects of the old level will be released and deallocated at that moment, since you (and the UIImageViews) don't have a strong reference to them anymore.
Pseudo implementation:
- (void)loadLevel:(int)levelNumber {
// Assuming you have your image views in an array imageViews.
// Determine the image files required for this level.
// Put them into an array imageNames.
for (int i = 0; i < 4; i++) {
if (imageNames.count < i) {
UIImage *image = [UIImage imageNamed:imageNames[i]];
[self.imageViews[i] setImage:image];
} else {
[self.imageViews[i] setImage:nil];
}
}
I'm displaying lots of images loaded directly from my app (not downloaded). My table view is slow when I scroll it the first time. It becomes smooth after all my cell has been displayed. I don't really know why.
I have an array of UIImage that I'm loading in the viewDidLoad. Then in my tableview delegate I just get the image at a given index path and set it to an UIImageView of my cell.
Do you know how I can improve performances ?
just to share I have fixed and it worked very well Steps I followed.
1) Set the performSelectorInBAckground function with Cell as parameter passed that holds the scroll view or uiview to put many iamges.
2) In the background function load the image stored from application bundle or local file using imagewithContents of file.
3) Set the image to the imageView using this code.
//// Start of optimisation - for iamges to load dynamically in cell with delay , make sure you call this function in performSelectorinBackground./////
//Setting nil if any for safety
imageViewItem.image = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *image = // Load from file or Bundle as you want
dispatch_sync(dispatch_get_main_queue(), ^{
//Set the image to image view not, wither cell.imageview or [cell add subviw:imageview later ]
[imageViewItem setImage:image];
[imageViewItem setNeedsLayout];
});
});
//// End of optimisation/////
This will load all images dynamically and also scroll the table view quite smoothly than previous slow and jerky behaviour.
All the best
You can read the answer I have just submitted here:
Loading image from CoreData at cellForRowAtIndexPath slows down scrolling
The basic idea is to use Grand Central Despatch to move your table-view-image-getting code to a separate thread, filling in your cells back on the main thread as the images become available. Your scrolling will be super-smooth even if there's a delay loading the images into memory from the filesystem.
What I understand from your question is that your images are all set to go and that they are loaded into RAM (stored in an array which is populated in viewDidLoad). In this case, I would want to see the exact code in cellForRowAtIndexPath in order to help out. My instinct tells me that something is being done there that shouldn't be done on the main thread (as He Was suggests). The thing is - if it's only a fetch from an NSArray (worst case O(log(n))), you shouldn't be seeing a performance hit.
I know you're not downloading the images but I would still recommend to do ANY non-UI operation on a background thread. I wrote something that might help you out.
I want to set an image as my background at runtime, so I created a UIImageView 'backGroundImage' as IBOutlet in Interface Builder. However the following code:
UIImage *image = [UIImage imageNamed:imageName];
NSLog(#"image=nil %d",(image ==nil));
NSLog(imageName);
[self.backGroundImage setImage:image];
NSLog(#"backgroundImage=nil %d",(self.backGroundImage.image ==nil));
gives me the following NSLogs
2012-06-15 12:09:43.967 iElster[1960:207] image=nil 0
2012-06-15 12:09:43.967 iElster[1960:207] bedroom-sketch-corel-eps-vector-art-1141.jpg
2012-06-15 12:09:43.968 iElster[1960:207] backgroundImage=nil 1
so basically I can load my image, but I can not set it as the image of the UIImageView. If I include the following line
backGroundImage = [[UIImageView alloc]init];
I don't get 'nil' anymore for my backGroundImage.image - but in that case I obviously don't use the UIImageView created by the Outlet anymore. I do not see the problem with my code above, especially since all I saw as suggestions in similar Stack Overflow threads was exactly the same: "simply use the setImage method"; Any ideas what could be the problem?
PS: just to clarify that - I tried using different images, both .png and .jpg and they all work if I e.g. just set them as a subview to my ScrollView, but not when trying to use them in the code above. So the image files shouldn't pose any problems.
It seems that you placed this code in a method that is executed before the view gets loaded and the outlets are connected (e.g., in init...). Try placing it in viewDidLoad. You cannot access your view outlets before the view gets loaded (i.e., before viewDidLoad is accessed).
Also, this line:
[image release];
is a memory management error (overrelease).