Hello I am noob in Xcode and I want to ask
I have array of UIView's with one image
NSInteger i;
for (i=0; i<CARD_AMOUNT; i++) {
[cardArray addObject:[UIImage imageNamed:#"Back.png"]];
}
and I want to draw it and then to do different things on each (move...etc)
or I must create 10 variables of UIImageView?
Could use a bit more explanation of the app you're trying to build, but essentially if you're wanting to display many different cards on one view (let's say, a game of solitaire) - you'll want to create many UIImageViews. Well, from an pure design perspective, you'd probably want a different class called CardView which knew how to render itself
So, if you wanted many different views all with one image, something like this:
for(int a=0; a<CARD_AMOUNT; a++) {
UIImageView *view = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Back.png"]];
[cardArray addObject:view];
// and maybe you'd want to set the frame locations, or add them to your current view as subviews, or ..
}
To address the comments, your custom CardView might have something like this (there's ways to optimize, but the general concept isn't too bad):
- (void)setRank: (int)rank andSuit:(int)suit {
self.image = [UIImage imageNamed:[NSString stringWithFormat:#"card-%d-%d.png",rank,suit];
self.rank = rank; self.suit = suit;
}
or, better yet (since a card's rank won't change) - some initWithRank: andSuit: method.. dunno.
There's probably 10 other ways to design this.
Related
I want to display some images in a scroll view, but I am facing some issues.
Here is what I have:
- (void)viewDidLoad
{
[super viewDidLoad];
myObject = [[NSMutableArray alloc] init];
[myObject addObject:#"tut_5.jpg"];
[myObject addObject:#"tut_2.jpg"];
[myObject addObject:#"tut_3.jpg"];
[myObject addObject:#"tut_4.jpg"];
pageControlBeingUsed = NO;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHight = screenRect.size.height;
for (int i = 0; i < [myObject count]; i++) {
CGRect frame;
frame.origin.x = self.takeTourScrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.takeTourScrollView.frame.size;
NSString *val = [myObject objectAtIndex:i];
UIImage* theImage = [UIImage imageNamed:val];
UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(screenWidth*i,0,185 ,284)];
img.image = theImage;
[self.takeTourScrollView addSubview:img];
}
The first first image seems to be ok.
Then when I swipe left I am getting a blank screen
I swipe left again and then I see my second picture. And it goes like that for all the 4 images.
Any ideas what am I doing wrong ?
There are system components that will give you what you want with much less work and a more refined user experience. Using a UICollectionView is one possibility, as mentioned by Vive in her answer. My suggestion would be a UIPageViewController. There is a sample app called PhotoScroller from Apple that shows how to implement a UI very much like what you describe. Do a search in the Xcode docs for "PhotoScroller" to find it.
I'd advise to use UITableView/UICollectionView for such behaviour. You can set the image in each cell and they'll be reused - it will be much better in terms of memory management.
The tableView will by the way place all elements in proper places (you just need to define the height of the cell or the layout).
It will also properly resize on events (eg phone rotation).
Also, you should try to avoid hardcoding the sizes:
CGRectMake(screenWidth*i,0,185 ,284)
You will fail in case of smaller / bigger devices. It'll be a great pain to maintain the code after some time.
-- edit --
After #Duncan C posted his answer, I've noticed you're looking for a paging system. You can go with building your own solution either on UIPageViewController or on UICollectionView. However you can also use third party libraries, I really enjoy this one: https://www.cocoacontrols.com/controls/bwwalkthrough. It has a support of different animations and does a ton of stuff for you :).
you already set the x position of uiscrollview. do not need to set the x postion of UIImageView.Because you used imageview as a subview of uiimagescrollview.
change this line to
UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(screenWidth*i,0,185 ,284)];
with
UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(0,0,185 ,284)];
I'm adding image in subviews of a scrollView, but only to a certain custom class AsyncImageView. This takes some time because there are a lot of subviews and the application has lost it's smooth scrolling.
NSArray *subviewsArray = [[NSArray alloc] initWithArray:[scrollView subviews]];
for (int j = 0; j < [subviewsArray count]; j++) {
for (AsyncImageView *checkView in [[subviewsArray objectAtIndex:j] subviews]) {
if ([checkView isKindOfClass:[AsyncImageView class]]) {
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:checkView];
NSString* urlTextEscaped = [[imageInfo objectAtIndex:0] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *URL = [NSURL URLWithString:urlTextEscaped];
if (URL)
{
checkView.imageURL = URL;
}
else
{
NSURL *defaultURL = [NSURL URLWithString:#"http://www.domain.com/user_avatar_default.jpg"];
checkView.imageURL = defaultURL;
}
}
}
}
Is there a better way to access the view that I need and add image to it? Some other logic that I haven't think of.
This is the hierarchy of the scrollView
scrollView
|
|
MaseterView
|
|
AsyncImageView - TitleView - OtherImageView
You don't need to create a new array with the subviews of the scroll view before you iterate it. You have 2 general options:
A. Still looping, but not done by you. Only meaningful if you have a single image view or have multiple image views that you want to update individually.
Set the tag of the image view, then use [scrollView viewWithTag:...];
B. If you have multiple image views and you want to update all of them at the same time.
Hold a mutable array property and add the image views to it when you create them.
Other things to think about:
Do you need to update all the image views - are they all on display?
Can you observe the scrolling of the scroll view and only start loads for images when they're on display (and cancel incomplete loads when scrolled off display)?
You can use
UIView *v = [self.containerView viewWithTag:uniqueTagvalue];
set unique tag value for the views you need and fetch it
Even though I’m writing in Objective C, most of my code is still written in a procedural style. However, now I want to do something where that approach will not work. So I need some advice on how to deal with an indeterminate number of objects on the screen at the same time. I’m sure that this problem has been solved, I just haven’t been able to find out how.
I have a bunch of games where I put two or four pictures on the screen and then the user interacts with the picture. When they are done with a page they swipe to the next one and I use a transition to slide the pictures off the screen. I can control the movement of the pictures because when they were created I name them self.picture_1 and self.picture_2. The movement method knows about them even though that method didn’t create them.
Now suppose I want to have an indeterminate number of pictures on the screen. I can’t call them self.picture_1. through self.picture_n because ObjectiveC won’t let you dynamically create variable names. But I still need to move them in a method where they weren’t created.
I can make it work with two techniques, neither of which seem ideal. First, I look at all the objects on the screen and then do something with the ones that I want to target. Note: pictures are in buttons.
for ( id subview in self.parentView.subviews ) {
if ( [subview isKindOfClass:[UIButton class]] ) {
UIButton *pictureButton = subview;
for (NSUInteger i=0; i<self.totalItems; i++) {
NSUInteger row = (i % 2) + 1;
NSUInteger column = (i/2) + 1;
NSString *pictureTitle = [NSString stringWithFormat:#"pictureR%iC%i", row, column];
if ( [pictureButton.titleLabel.text isEqualToString:pictureTitle] ) [pictureButton removeFromSuperview];
}
}
}
This works for removing them from the view, but gets cumbersome when I try to make the pictures slide off the screen.
The second way is to make an array that holds the picture objects when they are created. I’ve been playing with something like this.
self.gridImages = [NSMutableArray arrayWithCapacity:4];
for (NSUInteger i = 0; i < itemsOnScreen; i++) {
Word *word = [wordListArray objectAtIndex:i];
self.gridImages[i] = word.image;
}
And then to do things with the pictures I loop through the array.
for (NSUInteger i = 0; i < itemsOnScreen; i++) {
Picture *picture = self.gridImages[i];
// do something with picture
}
Neither of these methods seems ‘right’ so I’m wondering if there is a preferred method for manipulating an indeterminate number of objects on the screen?
#Hot Licks, that works, so I put you answer into an answer.
I have it working for checkBoxes. They are created by the main view controller and it passes in a tag. I'm using tags starting at 1000 for checkboxes, 2000 for pictures, etc. Just before I put the checkBox on the screen, I assign it the tag.
[self.checkBox setTag:checkBoxTag];
You could also use: self.checkbox.tag = checkBoxTag;
When it is time to remove the checkBoxes, I loop through all of the tags starting at 1000 up to the total number of items on the screen. I have warnings turned up to 11 so I need to cast the counter to an NSInteger.
- (void)removeCheckboxes {
for (NSUInteger i = 0; i < self.totalItems; i++) {
NSInteger tagNumber = 1000 + (NSInteger)i;
[ [self.parentView viewWithTag:tagNumber] removeFromSuperview];
}
}
I would like to implement an app using a UIScrollView with paging, similar to the apple weather app.
But I am a little concerned about performance. The example implementation I have been using loads all of the views then the application launches. After a certain point, once this prove slow?
I wonder how Apple's camera roll is dealing with this, where a user may have 100+ photos that can be scrolled through. Should I try to figure out a way to build the view only when it is needed? Or maybe there is a way to replicate the dequeue reusable cell technique from a UITableView, only for horizontal view loading, since each view will have the same layout.
By far the most efficient solution (and this is used in many photo-browsing apps such as Facebook, and probably the native Photos app too) is going to be to load the content on-demand, just as UITableView does. Apple's StreetScroller sample project should get you on the right track.
A very efficient solution, is to make sure to reuse any views whenever possible. If you are going to be simply displaying images, you could use a subclass of UIScrollView, and layout these reusable views within layoutSubviews. Here you could detect what views are visible and not visible and create the subviews as needed.
An example dequeuing function may look like:
- (UIImageView *)dequeueReusableTileWithFrame:(CGRect) frame andImage:(UIImage *) image
{
UIImageView *tile = [reusableTiles anyObject];
if (tile) {
[reusableTiles removeObject:tile];
tile.frame = frame;
}
else {
tile = [[UIImageView alloc] initWithFrame:frame];
}
tile.image = image;
return tile;
}
Where reusableTiles is just an iVar of NSMutableSet type. You could then use this to load fetch any currently offscreen image views and quickly and easily bring them back into view.
Your layoutSubviews may look something like:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect visibleBounds = [self bounds];
CGPoint contentArea = [self contentOffset];
//recycle all tiles that are not visible
for (GSVLineTileView *tile in [self subviews]) {
if (! CGRectIntersectsRect([tile frame], visibleBounds)) {
[reusableTiles addObject:tile];
[tile removeFromSuperview];
}
}
int col = firstVisibleColumn = floorf(CGRectGetMinX(visibleBounds)/tileSize.width);
lastVisibleColumn = floorf(CGRectGetMaxX(visibleBounds)/tileSize.width) ;
int row = firstVisibleRow = floorf(CGRectGetMinY(visibleBounds)/tileSize.height);
lastVisibleRow = floorf(CGRectGetMaxY(visibleBounds)/tileSize.height);
while(row <= lastVisibleRow)
{
col = firstVisibleColumn;
while (col <= lastVisibleColumn)
{
if(row < firstDisplayedRow || row > lastDisplayedRow || col < firstDisplayedColumn || col >lastDisplayedColumn)
{
UImageView* tile = [self dequeueReusableTileWithFrame:CGRectMake(tileSize.width*col, tileSize.height*row, tileSize.width, tileSize.height) andImage:YourImage];
[self addSubview:tile];
}
++col;
}
++row;
}
firstDisplayedColumn = firstVisibleColumn;
lastDisplayedColumn = lastVisibleColumn;
firstDisplayedRow = firstVisibleRow;
lastDisplayedRow = lastVisibleRow;
}
I used something similar to this to tile in areas of a line when I was working with an exceptionally large area of a scroll view and it seemed to work quite well. Sorry for any typos that I may have created when updating this for an image view instead of my custom tileView class.
I have 10 UIImageViews on the screen. They are all in an array called posArray. I also have another UIImageView that is dragged by user and can hit those other 10. What is the easies way of displaying a simple NSlog message if the one object hits any of the other 10?
Right now i'm using touchesBegin, touchesMoved to move my one object and this array below to test if the one objects hits any of the other ten.
I'm just thinking that there is an easier, less memory spending way, way of doing this for some reason.
for (int i = 0; i < [posArray count]; i++) {
UIImageView *tempPos;
tempPos = [posArray objectAtIndex:i];
if (CGRectIntersectsRect(red1.frame, tempPos.frame)) {
red1.center = CGPointMake(tempPos.center.x, tempPos.center.y);
NSLog(#"position piece touched");
}
}
You can also use fast enumeration to get some more speed. Also you can add a break statement after you found one match (if you just need one match):
for (UIImageView * tempPos in posArray){
if (CGRectIntersectsRect(red1.frame, tempPos.frame)) {
red1.center = CGPointMake(tempPos.center.x, tempPos.center.y);
NSLog(#"position piece touched");
break;
}
}