Graphical glitch in UIScrollView when autorotating device - ios

I have an application which has a fullscreen UIScrollView, and within it there are seven images. The images are also meant to be full screen, and the scroll view is set to enable pagination.
I have a method which either creates or moves the image views:
-(void)rebuildImageView{
// set up images
float screenW = self.view.bounds.size.width;
float screenH = self.view.bounds.size.height;
int numImgs = self.soundNames.count;
self.mainScrollView.contentSize = CGSizeMake(screenW * numImgs, screenH);
for(int i=0; i<numImgs; i++){
UIImageView* imageView = (UIImageView*)[self.mainScrollView viewWithTag:i+100];
if(imageView == nil){
imageView = [[UIImageView alloc] init];
imageView.tag = i+100;
[self.mainScrollView addSubview:imageView];
imageView.image = [UIImage imageNamed:[NSString stringWithFormat:#"image%d.jpg",i]];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
[imageView release];
}
imageView.frame = CGRectMake(i * screenW, 0, screenW, screenH);
}
// scroll to the current one
[self.mainScrollView scrollRectToVisible:CGRectMake(self.currentSound*screenW, 0, screenW, screenH) animated:YES];
}
I also have this on the view controller:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[UIView animateWithDuration:duration animations:^{
[self rebuildImageView];
}];
}
This code works fine when I autorotate while image 0 is being shown, but when I'm on image 7, you can briefly see most of image 6 when rotating. This video shows what's happening:
http://www.youtube.com/watch?v=1O3jOcTgVP8
Is there a better method I should use to reconfigure the scroll view and images when rotating the device?

Any frame changes put in the willAnimateRotationToInterfaceOrientation:duration method should automatically animate. So you could try removing it from the block?
Personally, I've had a lot more luck with this type of thing subclassing UIScrollView and putting the equivalent layout subview frame code in an override of the layoutSubviews method (don't forget to call super or you might end up with misplaced scroll bars).

Related

Issue with subviews overlapping

I'm trying to create a custom view which containes several images. I do that by adding them programmatically. The problem is that those subviews overlap each other and I can't find the way to change that. The only solution I can see is doing something like setting frames for each new image programmatically. I would be grateful if someone could tell me what is the best way to solve this issue.
for (id image in self.images) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.imageViews addObject:imageView];
[self addSubview:imageView];
}
If you wanna make your customView like UICollectionView you need a UIScrollView and add your subviews in it. Everytime when you add a subview change frame location so it could be something like this:
int xPosition = 0;
int yPosition =0;
for (id image in self.images) {
if (xPosition>self.view.frame.size.width) {
//give size to every imageView and every time in loop change the location
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(xPosition, yPosition, 50, 50)];
imageView.image = image;
yPosition = yPosition + 50;
[self.view addSubView:imageView];
}
else {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(xPosition, yPosition, 50, 50)];
imageView.image = image;
xPosition = xPosition + 50;
[self.view addSubView:imageView];
}
}
Without using Interface Builder your only real options are to change the frame or the center.
imageView.frame = CGRectMake(x coord, y coord, width, height);
This method lets you resize and move whereas changing the center lets you do just that, move the center of the view.
or
imageView.center = CGPointMake(x coord, y coord);
Or as recommended add constraints.

UI help for Horizontal Color Picker

I am trying to make color picker by reading pixel of imageview. Right now I have one imageview in the background and retrieving pixel color from it.
But now i am looking to shift my UI like this, so need help in how should I make this UI
Here's what I thought this will loo like
UIView->n no of. Imageview and arrow will be on top of it.
But then there are few things that I am not sure how will I do it
I have to keep some color unlock as level is crossed colors will get unlock
knob should not go outside the bound
I have to retrieve the color from the edge of the arrow so that I get the proper pixel value
Here is my suggestion to how to deal with this,
Create a list of images and the color values for each of those in a
NSDictionary like,
NSDictionary *colors = #{[UIColor redColor]: [UIImage imageNamed:#"red"], [UIColor greenColor]: [UIImage imageNamed:#"green"], [UIColor yellowColor: [UIImage imageNamed:#"green"]]}
Then create the imageview and add the images to the view and add tap gesture recognizer to each of the imageViews;
UIView *parentView = self.parentView;
CGFloat knobHeight = 20;
CGFloat colorViewHeight = 20;
CGFloat colorViewWidht = 20;
int index = 0;
for(UIColor *color in colors){
CGRect frame = CGRectMake(index * colorViewWidth, knobHeight, colorViewWidth, colorViewHeight);
UIImageView *imageView = [[UIImageView alloc] initWithFrame: frame];
imageView.image = colors[color];
[parentView addSubView:imageView];
UITapGestureRecognizer *tapGestureRecognizer = [UITapGestureRecognizer alloc] initWithTarget: self selector:#selector(tappedImage:)];
imageView.userInteractionEnabled = YES;
[imageView addGestureRecognizer: tapGestureRecognizer];
}
Initially add knob to the first imageView;
UIImageView *firstImageView = [[parentView subView] firstObject];
CGRect knobFrame = CGRectMake (CGRectGetMidX(firstImageView.frame) - knobWidth / 2, 0, knobWidth, knobHeight);
UIImageView *knobImageView = [[UIImageView alloc] initWithFrame:knobFrame];
[parentView addSubview: knobImageView];
Then, in the tap selector you could animate the knob change as;
- (void)tappedImage:(UITapGestureRecognizer*)tap{
UIImageView *tappedImageView = tap.view;
CGRect newKnobFrame = CGRectMake((CGRectGetMidX(tappedImageView.frame) - knobWidth / 2, 0 , knobWidth, knobHeight);
[UIView animateWithDuration:2.0 animations:^{
knobImageView.frame = newKnobFrame;
}];
}
You could also keep track of the color for which you want to lock selection. Then, do not enable the userInteraction for those images and not add the gesture recognizer. You could set alpha value to show that it is not possible to select it.
This code is directly typed, but it should give you the rough idea of how it could work.

Creating a scalable Image button in iOS. Without a blurry image.

What am I trying to do:
I want to make a button that scales up or down depending on it's current state which is determined by it either being big or small.
When button is big and I press it, it becomes small. When the button is small and I press it, it becomes big.
The button must contain a image of a QR code and subsequently be scannable by a QR reader.
My problem:
Is that the image is blurry when big. This isn't the case if I put the image inside a ImageView instead of a button.
The effect is best described here:
http://imgur.com/a/h3rJg#0
The Code:
Button/Image Declaration
UIImage* image = [QREncoder encode:user];
// declare image - QREncoder can be found on github
UIImage *strechedBackground = [image stretchableImageWithLeftCapWidth:220 topCapHeight:220];
UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
CGFloat qrSize = self.view.bounds.size.width - kPadding * 2;
CGRect largeFrame = CGRectMake(kPadding, (self.view.bounds.size.height - qrSize) / 2,
qrSize, qrSize);
imageView.frame = largeFrame;
[imageView layer].magnificationFilter = kCAFilterNearest;
//[self.view addSubview:imageView];
// add qr button to the view
self.qrButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.qrButton.frame = largeFrame;
[self.qrButton setBackgroundImage:image forState:UIControlStateNormal];
[self.qrButton addTarget:self
action:#selector(aMethod)
forControlEvents:UIControlEventTouchDown];
self.qrButton.contentMode = UIViewContentModeCenter;
[self.view addSubview:self.qrButton];
Button/Image Scale Function
- (void)aMethod{
// some variables concerned with button sizes and dimensions
CGFloat qrSize = self.view.bounds.size.width - kPadding * 2;
CGRect largeFrame = CGRectMake(kPadding, (self.view.bounds.size.height - qrSize) / 2,
qrSize, qrSize);
CGFloat smallButtonWidth = 33.0;
CGFloat smallButtonHeight = 33.0;
CGFloat largeButtonWidth = 264.0;
CGFloat largeButtonHeight = 264.0;
CGRect smallFrame = CGRectMake(self.view.frame.size.width - smallButtonWidth - kPadding/2, self.view.frame.size.height - smallButtonHeight - kPadding/2, smallButtonWidth, smallButtonHeight);
// a boolean ivar indicates the size of the button
if (!self.qrButtonIsBig){
NSLog(#"Button was Big");
// animate the view...
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
//self.qrButton.transform = CGAffineTransformMakeScale(3, 3);
// resting positon after animation
self.qrButton.frame = smallFrame;
}
completion:nil];
NSLog(#"Button is now Small");
}else{
NSLog(#"Button was Small");
// animate the view...
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.qrButton.transform = CGAffineTransformIdentity;
// resting positon after animation, bottom left with 20 px margin
self.qrButton.frame = largeFrame;
}
completion:nil];
NSLog(#"Button is now Big");
}
self.qrButtonIsBig = !self.qrButtonIsBig;
}
If you need your view to be sharper when it is resized then you need your CALayer class to be a CATiledLayer. you can subclass it in order to define how you will fill your tiles.
Check this question if you want to go this road
check this question if you don't want to use CATiledLayer
Since the ImageView was sharp and ButtonImage blurry. It makes sense to scale the imageview with an invisible button on top as a practical work around rather than trying to get the image to be sharp in the button.

Stopping UIScrollView at specific place while scrolling with pagingEnabled

I have the following code to create a scroll view with additional area at the beginning and the end of the images (50 points).
UIScrollView* scroll = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,200,100)];
scroll.contentSize = CGSizeMake(400,100);
UIImageView* img1 = [[UIImageView alloc] initWithFrame: CGRectMake(50,0,100,100);
UIImageView* img2 = [[UIImageView alloc] initWithFrame: CGRectMake(150,0,100,100);
UIImageView* img3 = [[UIImageView alloc] initWithFrame: CGRectMake(250,0,100,100);
//Adding images to ImageViews
scroll.pagingEnabled = YES;
[scroll addSubView:img1];
[scroll addSubView:img2];
[scroll addSubView:img3];
The first time I see the view, I will see the additional area on the left (0-50), then the first image (50-150) and then half of the second image (150-200).
When I swipe left, I want to see half of the first image on the right, the second image at the center, and half of the third image on the right.
When I swipe left again, I want to see the third image at center, with half of the second image on the left, and the additional area on the right.
Can it be possible?
It's possible as long as you define everything correctly. Make sure that the assigned content size is equal to the overall size that you wish to scroll through. Make sure that each page size is equal to the scroll view's frame. You can set the clipsToBounds to NO if the frame clips your subviews.
You can do this by adjusting the contentSize of UIScrollView
- (void)addImagesToScrollView{
//An offset from where imageViewStarts
CGFloat xOffset = 50.0f;
CGRect imageViewFrame = CGRectMake(xOffset, 0.0f, 100.0f, 100.0f);
for (NSString *imageName in #[#"image1.jpg",#"image2.jpg",#"image3.jpg"])
{
UIImageView *imageView = [[UIImageView alloc]initWithFrame:imageViewFrame];
UIImage *image = [UIImage imageNamed:imageName];
imageView.image = image;
[self.scrollView addSubview:imageView];
imageViewFrame.origin.x+=imageViewFrame.size.width;
}
CGSize contentSize = self.scrollView.frame.size;
//Content size is calculate from the imageViewFrame and an offset is added to it
contentSize.width = imageViewFrame.origin.x+xOffset;
[self.scrollView setContentSize:contentSize];
}
Source Code
You can scroll to a particular place in the scrollview using the following code
[YOURSCROLLVIEW setContentOffset:CGPointMake(x, y) animated:YES];

IOS: UIScrollView with an infinite paging view

I have this code for a scrollview to showe 3 images:
const CGFloat kScrollObjHeight = 150.0;
const CGFloat kScrollObjWidth = 320.0;
const NSUInteger kNumImages = 3;
- (void)layoutScrollImages
{
UIImageView *view = nil;
NSArray *subviews = [scrollView1 subviews];
// reposition all image subviews in a horizontal serial fashion
CGFloat curXLoc = 0;
for (view in subviews)
{
if ([view isKindOfClass:[UIImageView class]] && view.tag > 0)
{
CGRect frame = view.frame;
frame.origin = CGPointMake(curXLoc, 0);
view.frame = frame;
curXLoc += (kScrollObjWidth);
}
}
// set the content size so it can be scrollable
[scrollView1 setContentSize:CGSizeMake((kNumImages * kScrollObjWidth), [scrollView1 bounds].size.height)];
}
- (void)viewDidLoad
{
self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];
// 1. setup the scrollview for multiple images and add it to the view controller
//
// note: the following can be done in Interface Builder, but we show this in code for clarity
[scrollView1 setBackgroundColor:[UIColor blackColor]];
[scrollView1 setCanCancelContentTouches:NO];
scrollView1.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView1.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
scrollView1.scrollEnabled = YES;
// pagingEnabled property default is NO, if set the scroller will stop or snap at each photo
// if you want free-flowing scroll, don't set this property.
scrollView1.pagingEnabled = YES;
scrollView2.pagingEnabled = YES;
scrollView3.pagingEnabled = YES;
// load all the images from our bundle and add them to the scroll view
NSUInteger i;
for (i = 1; i <= kNumImages; i++)
{
NSString *imageName = [NSString stringWithFormat:#"image%d.jpg", i];
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[scrollView1 addSubview:imageView];
//[scrollView2 addSubview:imageView];
//[scrollView3 addSubview:imageView];
[imageView release];
}
[self layoutScrollImages]; // now place the photos in serial layout within the scrollview
}
But now I want to do a scrollview that when is in last image show me after the first image of scroll view and the same thing when I have the first image if I go back, it must show the last image; so I want create a paging loop.
The basic idea is to set yourself as a UIScrollViewDelegate and apply some modulo arithmetic to the scroll position in order to wrap it around.
There are two basic variations on the idea. Suppose your images are A, B, C, so you currently have them within the scrollview ordered as ABC.
In the more logically pleasing solution — especially if you had lots and lots of images — you watch the scroll position and as soon as it gets to a position where the view is being pushed rightward and C has left the screen, you reorder the images as CAB and shift the current scroll position one spot to the right so that the move is invisible to the user. To put that another way, the scroll position is restrained to an area of two screens, centred on the middle of B (so, you get all of B and half a screen either side). Whenever you wrap it from somewhere on the left to somewhere on the right you shift all your image views one place to the right. And vice versa.
In the slightly easier to implement variation, instead of creating a scroll view with images arranged ABC, arrange then as CABCA. Then apply the same wrap around logic but to a central area of four screens and don't do any view reshuffling.
Make sure you use just setContentOffset: (or the dot notation, as in scrollView.contentOffset =) as a setter. setContentOffset:animated: will negate velocity.

Resources