Programmatically getting the "Z" position of a UIView compared to it's siblings - iphone

My class has logic for inserting views in certain levels in the hierarchy.
containerView = [[UIView alloc] init];
shadowView = [[UIView alloc] init];
contentView = [[UIView alloc init];
[containerView addSubview:shadowView];
[containerView insertSubview:contentView belowSubview:shadowView];
Later down the line, it flips them so the shadow view is below the content view instead of above it.
// "Flipping" their positions.
[containerView insertSubview:contentView aboveSubview:shadowView];
UIView has a subviews property returning an NSArray, unfortunately the array does not reflect the view stack ordering.
I want to unit test the placement of the view compared to it's siblings. How can I do that?
Here's an example of a test.
- (void)testViewHierarchyFlipping {
STAssertEquals(containerView, shadowView.superview, nil);
STAssertEquals(containerView, contentView.superview, nil);
// Test that shadowView is ABOVE contentView.
}

You can inspect the containerView.subviews property to determine the Z position of each subview.
Items at the beginning of the array are deeper than items at the end of the array.
I believe this should work for your test, although I don't have access to Xcode to verify that it's correct at the moment.
- (void)testViewHierarchyFlipping {
STAssertEquals(containerView, shadowView.superview, nil);
STAssertEquals(containerView, contentView.superview, nil);
// Test that shadowView is ABOVE contentView.
UIView *topView = (UIView *)[[containerView subviews] lastObject];
UIView *bottomView = (UIView *)[[containerView subviews] objectAtIndex:0];
STAssertEquals(shadowView, topView, nil);
STAssertEquals(contentView, bottomView, nil);
}

Related

Set initial position of subview

So i want to add subview (UIImageView) to my main view with the following code:
NSString *fileLocation = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"background_main.png"];
_aImage = [[UIImage alloc] initWithContentsOfFile:fileLocation];
_aImageView = [[UIImageView alloc] initWithImage:_aImage];
_aImageView.frame = CGRectMake(-200, 0, _aImage.size.width, _aImage.size.height);
[self.view addSubview:_aImageView];
as you can see - i'm adding UIImageView to position (-200;0);
But it shows up at default position : (0;0).
My view hierarchy:
self.view -main view where i'm adding subview(UIImageView);
_aImageView - UIImageView that i'm adding.
Results:
NSLog(#"image is %#",_aImageView); //result (-200 0; 420 568)
CGPoint test = [_aImageView convertPoint:_aImageView.frame.origin toView:self.view];
NSLog(#"convert %f %f",test.x,test.y); //result (-400.000000; 0.000000)
I know that position is still (0;0) because i can see it when i testing my app. Later when i'm changing position of _aImageView to (-50;0) it's shows black screen. I know i'm missing something very important but i can't figure out why it shows up at (0;0) position.
Links to similar questions that i have checked:
How to addSubview with a position?
iOS: Add subview with a fix position on screen
what worked for me in the past in this situations is doing something like this :
NSString *fileLocation = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"background_main.png"];
_aImage = [[UIImage alloc] initWithContentsOfFile:fileLocation];
_aImageView = [[UIImageView alloc] initWithImage:CGRectMake(-200, 0, _aImage.size.width, _aImage.size.height)];
[_aImageView setImage:_aImage];
[self.view addSubview:_aImageView];
If this doesn't work try changing it's frame in viewDidAppear method (i assume you're adding it in viewDidLoad).
Last solution is to set the view's center after you add it on the screen.
CGPoint pt=CGPointMake(-200+_aImageView.frame.size.width/2,_aImageView.frame.size.height/2);
_aImageView.center=pt;
If that fails the view is changed in another part of your code or the view is added on the screen properly but the image is nil.
I believe you can't change the frame until in layoutSubviews. You could set the frame in layoutSubviews...
-(void)layoutSubviews
{
[_aImageView setFrame:CGRectMake(-200, 0, _aImage.size.width, _aImage.size.height)];
}
Or set the frame first then add the image wherever you're initializing the UIImageView...
_aImageView = [[UIImageView alloc] initWithFrame:CGRectMake(-200, 0, _aImage.size.width, _aImage.size.height];
[_aImageView setImage:_aImage];

Scrolling Images From NSMutableArray

I parse an XML in my blog app. When I parse it, I have created an object called entry, with properties for Title, Article, Link, Content, and Image. I then have a mutable array called entries that after parsing is finished, I add to entries the object entry. This way, to get the Title of a certain article in a tableview, I can call
RSSEntry *entry = [_Entries objectAtIndex:indexPath.row];
NSString *title = entry.Title;
What I would like to do is have a UIImageView above the tableview, and set that to scroll through all the different UIImages in the array, but am unsure of how to accomplish this. I know of the animationImages property, but not sure what to set as the NSArray in all this, since I use Mutable Array with properties. Any suggestions would greatly help.
There are two options:
a) collect all the images in the separate array by iterating through _Entries to set the animationImages array value;
b) prepare your own view, initialize the timer at constructor with [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:], change the displayed image at selector. To make a smooth animation you can use two imageView subviews setting animated the displayed imageView alpha from 1 to 0 and the next imageView alpha from 0 to 1. As the datasource you can use array of RSSEntry* taking the images from there with a known keypath.
The first option is fairly easier.
I hope you know how to put the UIImageView above the tableview in the xob/storyboard.
Than I am not sure what would you like to do with array of images, but if that mets your needs than it can be a swipe recognizer, and swipe the images
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
UISwipeGestureRecognizer* recognizerLeft = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeLeft)];
recognizerLeft.direction =UISwipeGestureRecognizerDirectionLeft;
recognizerLeft.numberOfTouchesRequired =1;
[self.view addGestureRecognizer:recognizerLeft];
[recognizerLeft release];
UISwipeGestureRecognizer* recognizerRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeRight)];
recognizerRight.direction =UISwipeGestureRecognizerDirectionRight;
recognizerRight.numberOfTouchesRequired =1;
[self.view addGestureRecognizer:recognizerRight];
[recognizerRight release];
}
- (IBAction)swipeLeft{
//NSLog(#"Swipe left");
if(pager.currentPage < pager.numberOfPages - 1){
pager.currentPage = (pager.currentPage + 1 );
[self pagerValueChanged];
}
}
- (IBAction)pagerValueChanged
{
NSData* imgData = [images objectAtIndex:pager.currentPage];
UIImage* img = [[UIImage alloc]initWithData:imgData];
imageView.image = img;
//NSLog(#"img width: %f, img height: %f", img.size.width, img.size.height);
[img release];
}
I have paging control and other controls too in my code, but you probably don't need those
This is pretty easy using array enumeration. The following code will take a scroll view that you have already configured and add image views to it for all the objects in your array. It will also dynamically adjust the content size of the scroll view based on the count of the array.
[myMutableArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
UIImageView *myImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[myMutableArray objectAtIndex:idx]]];
[myImageView setFrame:CGRectMake(myScrollView.frame.size.width * idx, myScrollView.frame.origin.y, myScrollView.frame.size.width, myScrollView.frame.size.height)];
[myScrollView addSubview:myImageView];
[myScrollView setContentSize:CGSizeMake((myScrollView.frame.size.width * idx) + myScrollView.frame.size.width, myScrollView.frame.size.height)];
}];

How to dynamically add a UIImageView to a view and position items?

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.

Animation stops when using UINavigationController or UITabView

I have a strange bug that I can't seem to figure out. I'm creating a little shining animation, which works perfectly, but for some reason stops when I navigate to another view via UINavigationController or UITabView (strangely modal view's don't affect it). Any ideas why, and how I can make sure the animation doesn't stop?
UIView *whiteView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[whiteView setBackgroundColor:[UIColor whiteColor]];
[whiteView setUserInteractionEnabled:NO];
[self.view addSubview:whiteView];
CALayer *maskLayer = [CALayer layer];
maskLayer.backgroundColor = [[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"ShineMask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-whiteView.frame.size.width,
0.0f,
whiteView.frame.size.width * 2,
whiteView.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:self.view.frame.size.width * 9];
maskAnim.repeatCount = HUGE_VALF;
maskAnim.duration = 3.0f;
[maskLayer addAnimation:maskAnim forKey:#"shineAnim"];
whiteView.layer.mask = maskLayer;
Your maskAnim is retained by maskLayer, which is retained by whiteView's layer, which is retained by whiteView, which is retained by self.view. So this entire object graph will live until your view controller's view gets dealloc'd.
When you navigate away from your view controller, UIKit unloads your view to free up memory. When the view gets dealloc'd, so does your maskAnim. When you navigate back to your view controller, UIKit reconstructs your view hierarchy, either by reloading it from its .xib, or by calling loadView, depending on which technique you used.
So you need to make sure that the code you used to set up your maskAnim gets called again after UIKit reconstructs your view hierarchy. There are four methods you might consider: loadView, viewDidLoad, viewWillAppear:, and viewDidAppear:.
loadView is a sensible option if you use that method to build your view hierarchy (as opposed to loading it from a .xib), but you'll have to change your code so that it doesn't depend on the self.view property, which would trigger loadView recursively. viewDidLoad is also a good choice. With either of these options you you need to be careful because UIKit might resize your view after viewDidLoad if it wasn't constructed at the right size. This could cause bugs since your code depends on self.view.frame.size.width.
If you set up your animation in viewWillAppear: or viewDidAppear:, you can be sure that your view's frame will be the proper dimension, but you need to be careful with these methods because they can get called more than once after the view gets loaded, and you don't want to add your whiteView subview more than once.
What I'd probably do is make whiteView a retained property, and lazy load it in viewWillAppear: like this:
- (UIView *)setupWhiteViewAnimation {
// Execute your code above to setup the whiteView without adding it
return whiteView;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!self.whiteView) {
self.whiteView = [self setupWhiteViewAnimation];
[self.view addSubview:self.whiteView];
}
}
- (void)viewDidUnload {
self.whiteView = nil; // Releases your whiteView when the view is unloaded
[super viewDidUnload];
}
- (void)dealloc {
self.whiteView = nil; // Releases your whiteView when the controller is dealloc'd
[super dealloc];
}

iphone: uiscrollview doesn't scroll? but contentsize is set..?

hey people,
I have some data and want to show this in a new view inside a navigation-stack.
I created some new viewcontroller class with XIB file. Then I edited the XIB file and placed the following hierachy:
1 files owner
2 first responder
3 scrollview
3.1 view
3.1.1 label1
3.1.2 label2
3.1.3 label3
3.1.4 image4
I arranged the labels and the image. the content of the labels may differ and they should size dynamically. I also did the IBOutlets in the viewcontroller class and connected everything correctly. the files owner's view delegate is set to view..
then I load the viewcontroller and I do in the "viewDidLoad" - method the following:
- (void)viewDidLoad {
[super viewDidLoad];
self.currentPageURL.text = [self.offerItem objectForKey: #"page-url"];
self.currentPrice.text = [self.offerItem objectForKey: #"price"];
self.currentRank.text = [self.offerItem objectForKey: #"rank"];
self.currentName.text = [self.offerItem objectForKey: #"name"];
self.currentProducerName.text = [self.offerItem objectForKey: #"producer-name"];
self.currentDescription.text = [self.offerItem objectForKey: #"description"];
self.imageViewForImage.image = [helperClass resizeImage:[self.offerItem objectForKey: #"picture"] forSize:CGSizeMake(280.0, 280.0) ];
[theScrollview setScrollEnabled:YES];
[theScrollview setContentSize:CGSizeMake(320, 1200)];
[theScrollview flashScrollIndicators];
[theScrollview addSubview:theView];
}
when I start the app in my simulator, everything is shown, but if I try to scroll, nothing happens..
what do I do wrong?
Autolayout set some constraints after the view loads, so, pick your code that sets the content size in viewDidLoad and put in viewDidAppear, and your code won't be rewritten.
This worked for me.
-(void)viewDidAppear:(BOOL)animated{
[self.scrollView setContentSize:CGSizeMake(320, 1000)];
}
and sorry for my poor english.
Ensure that "Use Autolayout" is unchecked in the IB Document attributes. I had the exact same issue, setting the correct contentSize and all, but with this feature enabled, scrolling was always disabled.
UPDATE To use Autolayout, explicitly set the width and height of the scrollView's content in the viewDidAppear method, like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// set the scrollview to the specified size
CGRect screenRect = [[UIScreen mainScreen] bounds];
[scrollView setContentSize:CGSizeMake(screenRect.size.width, 1100)];
[scrollView setBounds:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)];
[scrollView setScrollIndicatorInsets:UIEdgeInsetsFromString(#"{0,0,0,-1.0}")];
}
Immediately change the frame of theScrollView to 320x480 and you should see scrolling.