How to swap UIScrollViews? - objective-c

Let's imagine that I have 2 UIScrollViews with different UIImageViews inside. When I trigger an action I would like the contents, parameters etc (besides the location) of the 2nd UIScrollView to be passed onto the 1st UIScrollView.
So, here's the code that I've come up with:
-(void) someAction {
UIScrollView * scroll = [[UIScrollView alloc] init]; // create an intermediary scrollView
scroll = secondScroll; // here I intent to pass the content of secondScroll to scroll
scroll.frame = firstScroll.frame; // here I assign the frame of firstScroll to scroll
so that it doesn't take the frame of secondScroll;
firstScroll =scroll; // and then pass all the contents of scroll to firstScroll;
}
But when I execute that and trigger the action, the firstScroll seems to take the content of secondScroll, but secondScroll seems to be deleted. I need it to stay as is.
Any help?
Thanks

-(void) someAction {
UIScrollView * scroll = [[UIScrollView alloc] init]; // create an intermediary scrollView
That's WRONG. You'll be leaking memory with it, since you're losing the pointer to that scroll view after assigning it to a different object. You'd better simply declare UIScrollView *scroll; for that purpose.
scroll = secondScroll; // here I intent to pass the content of secondScroll to scroll
But that's not what happens. The scroll views don't magically copy themselves; if you assign one to another, you only assign the pointer.
scroll.frame = firstScroll.frame; // here I assign the frame of firstScroll to scroll
// so that it doesn't take the frame of secondScroll;
So, from now on, if you operate on scroll, it will effect secondScroll.
firstScroll = scroll; // and then pass all the contents of scroll to firstScroll;
And with this, you assign firstScroll to SecondScroll. So firstScroll will now essentially be secondScroll, losing it (leaking memory again and again) and having done nothing useful.
}
What on Earth are you trying to do with this?
Assuming you want to exchange the two views, you should do something like this instead:
BOOL firstScrollVisible = NO; // in -init or whatever
- (void) someAction
{
if (fistScrollVisible)
[self.view bringSubviewToFront:secondScroll];
else
[self.view bringSubviewToFront:firstScroll];
firstScrollVisible = !firstScrollVisible;
}

Try doing the following:
- (void)someAction {
[firstScroll removeFromSuperview]; // So that we do not leave the old firstScroll as a subview
firstScroll = [secondScroll copy]; // Makes a copy of secondScroll and stores it in firstScroll
[self addSubview:firstScroll]; // Add the new firstScroll to the view
}
Important Note(s):
This code will leak memory, as -copy returns an object with a reference count of +1, but we do not release firstScroll. I highly suggest you make firstScroll and secondScroll into nonatomic, retain properties (#property (nonatomic, retain) UIScrollView *firstScroll). If you want more info on this, just ask. Furthermore, you will need to make sure that firstScroll and secondScroll are never garbage values when this code gets run (it will crash) - depending on when and how it is called you may be fine.

If you want to clone a UI element, you use NSCoder (just like the nib file loader does).
NSData *scrollViewData = [NSKeyedArchiver archivedDataWithRootObject:self.firstScroll];
CGRect oldFrame = self.secondScroll.frame;
self.secondScroll = [NSKeyedUnarchiver unarchiveObjectWithData:scrollViewData];
self.secondScroll.frame = oldFrame;
This serializes the UIView and all its subviews into an NSData and then creates a complete copy of those from the NSData.

#jrtc27 Thanks for your answer. I think I've found a solution and it seems to accomplish what I'm looking for. But I'm not sure if it's the right way or not. Here's what I do:
- (void)someAction {
[[firstScroll subviews] makeObjectsPerformSelector:#selector(removeFromSuperView:);//here I sort of blank out the content of the firstScroll
NSArray * array = [secondScroll subviews];//place all subview of the secondScroll in array
int i = 0;
int nrOfSubviews = [array count];
for (i=0;i<nrOfSubviews;i++) { //appoint them one by one to firstScroll
[firstScroll addSubview:[array objectAtIndex:i]];
}
}

Related

Sorting NSCollectionViews bound Array Controller -> Explicitly Animate NSCollectionViewItems frame

I have an NSCollectionView which is bound to an NSArrayController via Interface Builder.
I provide NSSortDescriptors and NSPredicates to filter and sort the array (which totally works), however, since I am relying on the implicit animation via NSAnimationContext the only key my CALayers are requested to animate is #"hidden". I would like to animate the frame origin so the cells visually move to their new positions.
In the past I've used the collection views animator property to performBatchUpdate's and animate insertions, deletions and moves which resulted in #"frameOrigin" animations to be triggered. It doesnt seem to work that way using bindings?
Is it possible when specifying a sort or filter on the ArrayController which is backing my NSCollectionView via binding, to force explicit frame animation on its items?
Here is my current sorting code, for reference:
- (void) setupSortUsingSortDescriptor:(NSSortDescriptor*) sortDescriptor selectedItem:(SynopsisMetadataItem*)item
{
NSAnimationContext.currentContext.allowsImplicitAnimation = YES;
NSAnimationContext.currentContext.duration = 0.5;
[NSAnimationContext beginGrouping];
self.resultsArrayControler.sortDescriptors = #[sortDescriptor];
[self updateStatusLabel];
if(item != nil)
{
NSUInteger index = [self.resultsArrayControler.arrangedObjects indexOfObject:item];
if(index != NSNotFound)
{
NSIndexPath* newItem = [NSIndexPath indexPathForItem:index inSection:0];
NSSet* newItemSet = [NSSet setWithCollectionViewIndexPath:newItem];
[self.resultsArrayControler setSelectionIndex:index];
[self.collectionView.animator scrollToItemsAtIndexPaths:newItemSet scrollPosition:NSCollectionViewScrollPositionCenteredVertically];
}
}
[NSAnimationContext endGrouping];
}
Thanks, any insight is appreciated!
It is really hard to help without seeing all the code and settings for Collection View. It could have different styles and layout. Collection View with "content array" layout animates items out of the box as soon as you change sortDescriptors of bound NSArrayController.
I created an example for you in Swift (cannot do objective-c sorry):
https://github.com/emankovski/AnmatedCollectionSort

Correctly Load an Image into an Existing storyboard UIImageView?

I am using an array to try to load images into specific UIImageViews. I have 10 UIImageViews set up on my storyboard.
I also have a Game Over method that runs whenever the frame of one UIImageView intersects with another UIImageView. For some reason, whenever I try to use the array to load my UIImageViews, my Game Over method immediately triggers. I can't figure out why. As soon as I remove the initWithImage line, the game runs normally. Any ideas?
- (void)viewDidLoad
{
KanaCharacters = [[NSArray alloc] initWithObjects:[UIImage imageNamed:#"a.png"],...
nil];
int imageDisplay = arc4random_uniform([KanaCharacters count]);
Monster1 = [[UIImageView alloc] initWithImage:[KanaCharacters objectAtIndex:imageDisplay]];
}
-(void) Collision
{
if ((CGRectIntersectsRect(Monster1.frame, Ship.frame)) && (Monster1Hit==NO))
{
[self GameOver];
}
}
-(void)GameOver
{
WinOrLose.hidden=NO;
WinOrLose.text = [NSString stringWithFormat:#"You Lose!"];
}
I suspect that Collision if firing once an image in Monster1 and Ship.frame.
This is because I suspect your Monster is being placed where your ship is?
you may want to move the ship or the monster before you add the image into the view.

UIView creation and positioning

I have in my controller two UIView members, progressLineView and buttonsView. At some point I call this method:
- (void) drawPlayProgressLine{
progressLineView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 1, buttonsView.frame.size.height)];
progressLineView.backgroundColor = [UIColor whiteColor];
[buttonsView addSubview:progressLineView];
}
Everything works fine, and I also have a method that changes the position of the view:
- (void) moveProgressLine{
CGRect frame = progressLineView.frame;
frame.origin.x++;
progressLineView.frame = frame;
}
After the moveProgressLine method is called a few times and I want to call drawPlayProgressLine again, instead of completely moving the view to the starting position, it creates a new view. The more drawPlayProgressLine is called, the more views I get on my screen but I only need one.
I don't understand how this can happen when I'm creating only one object. How can I move the view instead of having a new one created each time? And another question: how can completely remove it (until the drawPlayProgressLine method is called to create it again)
I don't understand how this can happen when I'm creating only one object.
You create a new view every time you call your -drawPlayProgressLine method. Call it 10 times, you get 10 views.
How can I move the view instead of having a new one created each time?
Don't create the view each time through -drawPlayProgressLine. Instead, you can do either of:
Create progressLineView once, when the view controller's view hierarchy is created. -viewDidLoad is a perfect place for that sort of thing.
Check the value of progressLineView and create it only if it is currently nil.
Whichever you choose, assuming progressLineView is an instance variable, you can do exactly what you're doing in your -moveProgressLine method. That is, just use progressLineView as though it already exists, because it does. BTW, an easy way to move a view is to modify it's center property:
CGPoint *c = progressLineView.center;
c.x += 25.0;
progressLineView.center = c;
And another question: how can completely remove it (until the
drawPlayProgressLine method is called to create it again)
One approach is to simply hide the view when you're not using it. Another is to remove it from its super view (and release it if you've retained it), and then set your progressLineView to nil. So, if progressLineView is an ivar, do this:
[progressLineView removeFromSuperview];
[progressLineView release]; // if you're not using ARC and have retained it
progressLineView = nil;
you should just check if its created yet before creating it and move it if necessary:
- (void) drawPlayProgressLine{
if(progressLineView == nil)
{
progressLineView = [[UIView alloc] init];
progressLineView.backgroundColor = [UIColor whiteColor];
[buttonsView addSubview:progressLineView];
}
progressLineView.frame = CGRectMake(0.0, 0.0, 1, buttonsView.frame.size.height);
}
You are probably not invalidating the parent view. New objects are not created, but rather their presentation is left on the screen after you move them.
As for the second question:
[progressLineView removeFromSuperview];
[progressLineView release];
progressLineView = nil;

Apply a shadow to an array of UIImageView

I have an array of UIImageViews. I want to apply a shadow to each of these images. I've used the code below:
- (void)awakeFromNib {
for (UIImageView *image in imagesJigsawPieces) {
image.layer.shadowColor = [UIColor blackColor].CGColor;
image.layer.shadowOffset = CGSizeMake(-1, -1);
image.layer.shadowOpacity = 1;
image.layer.shadowRadius = 5.0;
image.clipsToBounds = NO; //EDIT: I have also included this with no change
}
}
I have also included #import <QuartzCore/CALayer.h>.
I am not getting any errors but I am also not getting any shadows on my images.
Are you certain this code is being called? Have you placed a breakpoint in the for loop to verify?
-awakeFromNib is called only if you have a view (or whatever) in a nib file connected via IBOutlet to an ivar in your code. -awakefFromNib is called, in this case, instead of -initWithFrame: (or the like), an important distinction which I sometimes forget myself!

How to refresh view in objective-c

I wanted to create a gallery. It loads different images based on the category, that a user selects. I used to populate images in UIImageViews.
My when selecting different categories is that it does not clear the previously selected images. This is my code for populating images.
-(void)refresh{
categoryLabel.text = treatment.treatmentName;
NSMutableArray *galleryImages =[NSMutableArray alloc] ;
galleryImages = [[PatientImage alloc]find:treatment.treatmentId];
int imgCount = [galleryImages count];
for(int i=0;i<imgCount;i++){
PatientImage *apatientImage = [galleryImages objectAtIndex:i];
UIImage *img1 = [UIImage imageNamed:apatientImage.imageBefore];
UIImageView *myImageView = [[UIImageView alloc] initWithImage:img1];
myImageView.contentMode = UIViewContentModeTopRight;
myImageView.frame = CGRectMake(120+i*240,120.0,100.0, 100.0);
[self.view addSubview:myImageView];
UIImage *img2 = [UIImage imageNamed:apatientImage.imageAfter];
UIImageView *myImageView2 = [[UIImageView alloc] initWithImage:img2];
myImageView2.contentMode = UIViewContentModeTopRight;
myImageView2.frame = CGRectMake(120+i*240+300,120.0,100.0, 100.0);
[self.view addSubview:myImageView2];
}
}
First things first, You have some memory leaks there. You are allocating UIImageViews but are not releasing them anywhere, after you have added them to your view. I don't know if that applies to ARC, though. Same applies to your Mutable array, but I suppose you are releasing it after the 'for' loop somewhere, since it seems you posted code after omitting some of it.
As far as your actual question is concerned, I wouldn't do this this way. I would make the mutable array an object variable, and then fill it with my image views. When calling refresh again, I would first call -removeFromSuperview on each image view, then empty the array, then repopulate it and add the new subviews to my view. That is the simple way.
I don't know if you are using ARC, but you should be careful about memory management when using dynamically loaded views. Each time you add a view to another one, you increase its retain counter. You must then call release to remove ownership, and let the iOS runtime handle the rest.
Also note that operations such as this using views are expensive in terms of memory. So, another way of repopulating the gallery view is to just change the image an imageView holds. That will save you some memory, and time. In case the view doesn't have a constant number of images to be displayed, you can refine your algorithm to change the images on the already created image views, and then add more image views if necessary or delete the remaining ones, if any.
I hope I helped.
try at the start of refresh call
[[self subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
or
for (id imageView in self.subviews){
if([imageView isKindOfClass:[UIImageView class]]) [imageView removeFromSuperview];
}
call [tableview reloadData] if You are using tableview to show your gallery images
or call view's
[self.view setNeedsDisplay] method for refreshing the view.