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
Related
I want the UIPageViewControllerNavigationOrientationHorizontal to scroll from the left instead of the right (the opposite way of the default) in a page view controller.
Can anyone help?
I've been in this situation before and couldn't figure it out using the UIPageViewController. I'm not sure what your intent on your project is but this might help.
Use a UIScrollView and place the amount of UIView's you need inside. At the point you could just create a CGPoint and set your UIScrollView's content offset to your CGPoint. This way you would start at the last page(view) and can scroll to the left instead of the right. Something like this below. You can perform this in your viewDidLoad method.
CGPoint nameYourPoint = CGPointMake(640.0,568,0);
self.yourScrollViewName.contentOffset = nameYourPoint;
I was able to figure it out fairly simply:
When adding objects into the array of images, I added them in backwards like so:
for (int i = 1; i <= 81; i++) {
NSString *imageName = [NSString stringWithFormat:#"image%i.gif", i];
[self.mArray insertObject:[UIImage imageNamed:imageName] atIndex:0];
After that, in the root view controllers viewDidLoad method, I simply started the images at the end of the array
ModelController *objModelController = [ModelController new];
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:[objModelController.mArray count]-1 storyboard:self.storyboard];
note:for some reason the length wasnt working, so I had to just add 2 empty objects to the beginning of the array and use
[objModelController.mArray count]-1
Hope this helps everyone out!
I need to take the current value of mtype and pass it forward to Mselect so that the image pushed forward is the same as the one rotating in the animation.
here is the code sample
- (void)viewDidLoad
{
//Array to hold Images
NSMutableArray *imageArray = [[NSMutableArray alloc] initWithCapacity:Mush_Count];
for (mtype = 1; mtype < Mush_Count; mtype++)
{
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"Mush%i.png", mtype]]];
//make button
SelectBt.imageView.animationImages = [NSArray arrayWithArray:imageArray];
SelectBt.imageView.animationDuration = 5;
[SelectBt.imageView startAnimating];
Mselect = mtype;
}
}
-(IBAction)Selection:(id)sender{
[self Placement];
}
-(void)Placement{
if (Place1Occ == NO) {
[self Place1];
}
}
-(void)Place1{
if (Place1Occ == NO) {
Place1.image =[UIImage imageNamed:[NSString stringWithFormat:#"Mush%i.png", Mselect]];
Place1Occ = YES;
}
}
The animation loops just fine and the selection of the images works but it's not selecting the image the is currently on the screen it selects the last image on the Array.
Any suggestions?
for (mtype = 1; mtype < Mush_Count; mtype++)
{
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"Mush%i.png", mtype]]];
}
That loop is enough to build the array of images - you don't need to set the imageView's animation images until this loop completes and the array is populated.
As it stands, you reset the animation images each loop and set Mselect to type each iteration: this is why you always get the last image index stored in Mselect
This code should be after the for loop:
SelectBt.imageView.animationImages = [NSArray arrayWithArray:imageArray];
SelectBt.imageView.animationDuration = 5;
[SelectBt.imageView startAnimating];
As far as I know, you can't get the current frame of the animation directly - you have to create a timer when you start the animation and have this count the number of frames (by increasing your Mselect value. Then use that to load the current image
Is SelectBT a subclass of UIButton? if so is it the (id)sender coming from your IBAction?
If so then you can just grab the image directly from the SelectBT instance by calling sender.imageView.image
As an aside, objective-C convention is to capitalize ClassNames, but start instances with lowerCase like this ClassName *instanceOfClassName and you're likely to take flack for it around here if you don't adhere to that convention
Update
Could you bump your imageArray into a class variable or a property? It already contains all the fully loaded UIImages, which you can get back out by calling [imageArray objectAtIndex:i]
Then you just have to keep track of which numeric index is facing your user, or which index they tapped and pull the corresponding image.
Feel free to post your whole class if you'd like us to explain more.
So I have setup a UIView that contains a UIScrollView (and child content view) that has sub views that are series of UILabels and UIViews that grow and shrink depending on the content contained in them, all using AutoLayout from the Storyboard. This works when I have something like Label - Label - Label - View w/o any issues, however if I put an empty UIView in-between two labels and insert sub views on the UIView, I'm not seeing the results I'm expecting. I have the following layout in a storyboard:
...where the teal and blue views are labels that grow to infinite height and the orange view (optionsPanel) is an empty UIVIew that I later inject sub views into. The rest of the stuff on the window is UILabels and UISegment controls. Between each row of views I have a Vertical Space constraint with a constant of 8. This all worked beautifully until I had to put in the empty UIView and programmatically inject sub views. The code I would expect to work would be something like (optionsPanel is the orange colored UIView)...
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
...where the orange UIView would magically grow and everything would just get pushed around accordingly, however this is what I'm seeing:
...the orange UIView does not grow at all, and the other two top UIView have gone somewhere off the screen. So my next guess was to turn off the Autoresizing Mask using...
optionsPanel.translatesAutoresizingMaskIntoConstraints = NO;
...but I'm getting a result where everything appears to be working but the orange UIView (optionsPanel) has no height for whatever reason and looks like:
This is getting closer to what I would expect, so I thought I would force the height of the orange UIView using code like...
frame = optionsPanel.frame;
frame.size.height = lastTop;
optionsPanel.frame = frame;
...but this appears to have no affect on anything.
Purely guessing, I found that this code almost works, if I arbitrary set the optionPanel's origin to something much larger than the space that is needed....
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
lastTop += 10;
frame = optionsPanel.frame;
frame.size.height = lastTop;
frame.origin.y += 300; //some arbitrarily large number
optionsPanel.frame = frame;
..which gives this result:
...but apparently the AutoLayout has decided that the name label needs to take up the extra space. Its an ugly approach but if I could figure out how much space I need then I could just push everything down, if I had to. What's the secret to having a dynamic UIView between two dynamically sized labels and everything just work???
As #Timothy says, you need to manually add constraints to the subviews of the orange view if you want it to resize based on its contents—views don’t do this by default.
In general, if you’re using autolayout in a window, you should never be manually setting the frame of any view. Autolayout overrides any frames you set the every time it’s called, so even if you manage to manually get it working for a second it’ll fail the next time anything triggers a layout.
For views created in code, it's perfectly fine to set their frames as long as their translatesAutoresizingMaskIntoConstraints property is YES (the default, by the way).
However, for a view instantiated in storyboard or a nib, you can not set its translatesAutoresizingMaskIntoConstraints to YES.
I'm developing an app for iPhone and I have a strange problem. I tried to solve this by myself but after 3 days I didn't found a solution anyway.
I have a scrollview in which I dynamically create other views and subviews, this is the code:
for (int i=0; i<dim; i++) {
UITextView *posted_nick= [[UITextView alloc] initWithFrame:CGRectMake(paddWidth, heightUpdateImageScrollview+paddHeight/2, screenWidth-2*paddWidth, 37)];
//textview customization...
[imagesScrollView addSubview:posted_nick];
row_images_like = [[UIView alloc] initWithFrame:CGRectMake(paddWidth,heightUpdateImageScrollview+paddHeight+37+heightImageInScrollView,screenWidth-2*paddWidth,80)];
//set the tag = id
row_images_like.tag = [id_image intValue];
UIImageView *like_mini = [[UIImageView alloc] initWithFrame:CGRectMake(0,15,25,25)];
//imageview customization...
//tag = id+1..
NSInteger x = [id_image intValue] + 1;
number_like.tag = x;
[row_images_like addSubview:like_mini];
UITextView *number_like = [[UITextView alloc] initWithFrame:CGRectMake(paddWidth*5/2,10,55,37)];
//textview customization...
//tag = id+2..
NSInteger x = [id_image intValue] + 2;
number_like.tag = x;
[row_images_like addSubview:number_like];
[imagesScrollView addSubview:row_images_like];
}
Now, all works great and when I click on the image view "like_mini", I can find the other views in the same row with the appropriate tag
(UIView *thisView = (UIView*)[imagesScrollView viewWithTag:ID_IMAGE];)
The problem is where I update my scrollview. When the user scrolls to the top, if there are new images to show, I call the same function that creates the views, and all the other views (that already exist) are moved some down. Why, when I try to find a view by tag in my scrollview, all works at the first time, but don't work for the new images created with the same code?
If i remove all the views in the scrollview, before adding the new views, it works. But i don't want to remove the oldest view.
When it works, I have in my console the view (row_images_like) with tag.
When it doesn't work, I receive a _UITextContainerView. What is this?
Hope I explained myself.
Hi there the only reason the images moves down is because you are not assigning the proper tags, please give appropriate value of tag to uiview, uiimageview and uitextview.
row_images_like.tag = [id_image intValue] + 1000;
For fetching the view get it done similarly what you did before only add thousand to it.
UIView *thisView = (UIView*)[imagesScrollView viewWithTag:ID_IMAGE+1000];
Also one error :
number_like.tag = x;
How does the above line object i.e "Number_like" comes before initialising it and change the tag value of other objects to "+2000" and "+3000"
Try removing all views added to scrollview, before loading scrollview again
Write this line above for loop
for (UIView *v in [imagesScrollView subviews])
{
[v removeFromSuperview];
v = nil;
}
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.