Advice for horizontal scroll view IOS - objective-c

I'm trying to create horizontal scroll view(s). Each view containing one image. [done]
Initially when app is opened I'd display couple of images i.e 3, so user can scroll back and forth between images. [done]
However I want to be able to go to another view controller and pick another image(s) maybe two for example and display five images in the scroll view instead of displaying 3 initially.
How would one do that? to "re-refresh" the initial scroll view ?
Update
Should I use delegate for this communication between view controllers? or how is this done?
1 main controller, other containing image selection?
This part above and much more explained by article here.(not advertising, I hope it will help someone as well).
Bounty update part 1 :
I think now that I found my way around delegates, I have additional question that I cannot find answer to, all examples I saw were about updating table views.
Bounty part 2 :
If I were to have a scrollview inside my view controller and some nsimages inside scroll view. i.e :
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *images = ...some images array;
for (int i = 0; i < images.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *subview = [[UIImageView alloc] initWithFrame:frame];
subview.image = [images objectAtIndex:i];
[self.scrollView addSubview:subview];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * images.count, self.scrollView.frame.size.height);
}
And let say my view controller implemented some delegate method didAddImage or didRemoveImage.
Meaning that about images array would get updated.
Actual bounty question :
How would one actually "tell" the view controller, O.K now your scrollview has one more image to display, please re-fresh or reload?
If I were to have a table view instead scroll view and was inserting images I'd do it something like this(in my delegate method) :
-(void) mockDelegatetablemethod:(...) ...{
[self.images addObject:image];
NSIndexPath *indexPath =
[NSIndexPath indexPathForRow:[self.images count] - 1
inSection:0];
[self.tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self dismissViewControllerAnimated:YES completion:nil];
}
How would one do this to the scrollview?
Bounty update part 3:
This is "simple" case described above, naturally I'd have to support removing images also as well as if one wants to remove all images and add some new.

How would one actually "tell" the view controller, O.K now your
scrollview has one more image to display, please re-fresh or reload?
Well, you would just add the new image as a subview of the scroll view. So something like:
- (void)addImage:(UIImage*)image {
[images addObject:image];
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * images.count;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *subview = [[UIImageView alloc] initWithFrame:frame];
subview.image = image;
[self.scrollView addSubview:subview];
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * images.count, self.scrollView.frame.size.height);
}
Or you could have a method that goes and "resets" the scroll view by removing all subviews and then re-adds all the images in your images array. Then call this method in viewDidLoad instead of your code there so that you're not duplicating code.
Basically, there's no silver bullet here. You roll your own.

You're going to need make your image scroller a class that you can tell to load up certain images. You'll need a reference to the existing image scroller, pass that pointer into your other "pick more images" controller. Then the "pick more images" controller can tell the image scroller that there is a new list of images.

This is already implemented by Apple in their iOS sample project "PhotoScroller". It has view reuse as well. They even have a WWDC 2010 video on this. I think its advanced scrollview techniques OR designing apps with scrollviews.
Shouldn't be hard to port over to mac

Related

Programmatically drawn UIView tags (or something alike)

I'm drawing a view that contains an xib, multiple times (same uiview) and then update the outlets each time it gets drawn displaying an nsmutablearray with different strings on the outlets
Clicking the view opens a viewcontroller that should be displaying the data from the view (+more), this works perfectly fine except it doesn't know which index of the array it's supposed to be displaying.
I'm trying to figure out a way to manage them in a way that I can see which view is being pressed so I can pass that 'id' onto the new vc (something to uniquely identify the view even though it's the same view being drawn)
Here's how the view is being drawn multiple times
- (void) populateUpcoming:(int)events {
[self resetVariables];
upcomingEventsCenterPos = self.view.frame.size.width / 2 - 159;
for (int i = 0; i < events; i++) {
upcomingEventsY2 = 175 * upcomingEvents2;
UIView *firstViewUIView = [[[NSBundle mainBundle] loadNibNamed:#"UpcomingEventFull" owner:self options:nil] firstObject];
[_scrollView addSubview:firstViewUIView];
CGRect frame = firstViewUIView.frame;
frame.origin.y = 9 + upcomingEventsY2;
frame.origin.x = upcomingEventsCenterPos + upcomingEventsX2;
firstViewUIView.frame= frame;
upcomingEvents2++;
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tapUpcomingEvent:)];
[firstViewUIView addGestureRecognizer:singleFingerTap];
}
[self setupScroll];
}
I think you can use UITableView and custom UITableViewCell for this purpose. On click of "Add More", add one entry to NSMutableArray and just reload tableview.
And no need to manage index separately, you can get it directly by indexPath.row
Also you don't need to manage scroll view size as tableview automatically adjust is content height according to row.
If you are using same kind of view,then go with this approach, as its easy and will save your time.

Scrolling the map to user's location or clicking on an annotation on the map causes the container view move back to the place set in the storyboard

I have
a view controller named MyMapViewController which is the delegate of the map view that is in the storyboard,
a view controller (i.e. a container view, 'myContainerView', in the storyboard) named MyListViewController that is the child view controller of MyMapViewController.
So, I have the following in the storyboard: http://pasteboard.co/1YAmM2ZU.png
In MyMapViewController, I set myContainerView's frame initially as follows so that the only part shown would be its navigation bar at the bottom of MyMapViewController:
CGRect destFrame = self.myContainerView.frame;
CGFloat navBarHeight = 44;
destFrame.origin.y = self.view.frame.size.height - navBarHeight;//to bottom of the screen; navigation bar yields 44 points
[UIView animateWithDuration:0
animations:^{
self.myContainerView.frame = destFrame;
}
];
In MyMapViewController, I set some annotations on the map and the delegate method is as follows:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
NSLog(#"welcome into the map view annotation");
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// try to dequeue an existing pin view first
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* pinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.animatesDrop=YES;
pinView.canShowCallout=YES;
pinView.pinColor=MKPinAnnotationColorPurple;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
UIImageView *profileIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"annotationProfile.png"]];
pinView.leftCalloutAccessoryView = profileIconView;
return pinView;
}
The problem is that when I scroll the map to user's own location or to the location of an annotation that is not initially shown on the map or click on one of the annotations, myContainerView shows up suddenly. (it moves back to the place set in the storyboard.) However, I want myContainerView to stay where it was.
I came up with some possible explanations why it happens but am not quite sure how I can overcome this problem. Any suggestions?
Thanks in advance.
When you face a user interface abnormality such as the one in the question, then there is surely something you miss about the design. If this is the case, check if you solve the problem by manipulating the constraints you defined in the storyboard. You can bring the constraint into the code just by pressing ctrl and dragging it in your code and thus create a property. Then you can easily change the constraint's constant value in your code, for example for moving the related view to where you want in superview.
If this would not work, try implementing another UI design approach. For instance, you could create a .xib file for the view. This way, you can easily manipulate every single move of your view inside its superview just by changing its frame inside the superview. This approach gives you a much more elasticity than the one that in storyboard, but harder to implement.

UIButton inside UIView doesn't respond to touch events

I've put a UIButton inside a custom UIView and the button is not receiving any touch events (it doesn't get into the highlighted state, so my problem is not about being unable to wire up a touch inside up handler). I've tried both putting it into the XIB in Interface Builder, and also tried programatically adding the UIButton into the UIView seperately, both ended with no luck. All my views are inside a UIScrollView, so I first though UIScrollView may be blocking them, so I've also added a button programatically exactly the same way I add my custom view into UIScrollView, and the button worked, elimination the possibility of UIScrollView could be the cause. My View's hiearchy is like this:
The button is over the image view, and the front layer isn't occupying my button completely, so there's no reason for me not be physically interacting with the button. At my custom view's code side, I'm creating my view as such:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIView *sub = [[[NSBundle mainBundle] loadNibNamed:#"ProfileView" owner:self options:nil] objectAtIndex:0];
[self addSubview:sub];
[sub setUserInteractionEnabled:YES];
[self setUserInteractionEnabled:YES];
CALayer *layer = sub.layer;
layer.masksToBounds = YES;
layer.borderWidth = 5.0;
layer.borderColor = [UIColor whiteColor].CGColor;
layer.cornerRadius = 30.0;
/*layer.shadowOffset = CGSizeZero;
layer.shadowRadius = 20.0;
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowOpacity = 0.8;
*/
}
return self;
}
I've tried all combinations of setUserInteractionsEnabled, and had no luck. (Yes, also set them to checked in Interface Builder too). I've also read in another question with a similar problem that I should try overriding 'canBecomeFirstResponder' to return 'YES' and I've also done that too. But the problem persists, I can't click the button. I've not given any special properties, settings to the button, it's just a regular one. My other objects in the view (labels below, image view behind the button etc.) are working properly without problems. What could be possibly wrong here?
Thanks,
Can.
UPDATE: Here is a quick reproduction of the problem: https://dl.dropbox.com/u/79632924/Test.zip
Try to run and click the button.
Looking at the test project, I believe your problem in the way you create TestView, you do not specify the frame for it, so basically the parent view is 0 size, and the subviews you see from XIB extending out of the parent view and thus do not get anything in responder chain.
You should either specify the frame when creating TestView, or adjust the frame after loading XIB file.
I have had this problem as well. The cause for me was that the UIButton superview frame was of height 0, so I believe that even though a touch was happening, it was not being passed down to the button.
After making sure that the button's superview took a larger rectangle as a frame the button actions worked.
The root cause for this problem on my side was a faulty auto layout implementation (I forgot to set the height constraint for the button's superview).
I've found the solution. I was initializing my custom view as:
MyView *view = [[MyView alloc] init];
I've initialized it instead with a frame of my view's size, and it started responding to events:
CGRect rect = CGRectMake(0,0,width,height);
MyView *view = [[MyView alloc] initWithFrame:rect];
Storyboard Solution
Just for anyone wanting a solution to this when using storyboards and constraints.
Add a constraint between the superview (containing the button) and the UIButton with an equal heights constraint.
In my case, I had selected embed UIButton in a UIView with no inset on the storyboard. Adding the additional height constraint between the UIButton and the superview allowed the UIButton to respond to touches.
You can confirm the issue by starting the View Debugger and visually confirm that the superview of the UIButton is not selectable.
(Xcode 11, *- Should also work in earlier versions)

UILabel scaling with UIScrollView

I have a UIScrollView that displays and Image and it scrolls fine and everything. What I want to do is add a UILabel to the UIScrollView to display the title of the image. I managed to do that, but when I zoom out the UILabel does not zoom with the scroll View and stays in the same place on the screen. How would I make it so the label scales with the scrollView Image? Here is the code I have:
[super viewDidLoad];
// Do any additional setup after loading the view.
self.scrollView.delegate = self;
//This just creates a image from a URL
NSURL * photoURL = [FlickrFetcher urlForPhoto:self.photoCellName format:2];
NSData * photoData = [NSData dataWithContentsOfURL:photoURL];
self.imageView.image = [UIImage imageWithData:photoData];
//Setting up scroll View
self.scrollView.contentSize= self.imageView.image.size;
self.imageView.frame = CGRectMake(0, 0, self.imageView.image.size.width, self.imageView.image.size.height);
NSLog(#"Name = %#", [self.photoCellName valueForKeyPath:#"description._content"]);
//Assigning title to the label
self.textLabel.text = [self.photoCellName objectForKey:#"title"];
Make sure label is the subview of your scrollview.
I'm guessing you are providing the UIImageView as the View that will be zoomed by the UIScrollView by implementing the method in the UIScrollViewDelegate.
If not, I'm not sure how your zooming is working then. If you are providing it, you'll have to return a UIView that contains as subviews your UILabel and your UIImageView and you will have to manually apply transformations to the UIView to resize it.
I guess that a similar question was answered in this SO thread, and Dimme (the one that answered it)provided a complete solution with source code, hope it helps!
You should check if you setup autoresizingMask property of your UILabel
self.label.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
You can do it in IB too. Then if you will change the frame of its superview the frame of your label will be updated during - (void)setNeedsLayout handling process
... and DO NOT block the main thread creating images from url in - (void)viewDidLoad!

UITableViewCell subclass, drawn in code, animate Delete button in

I'm working on a custom UITableViewCell subclass, where everything is drawn in code rather than using UILabels etc. (This is part learning exercise and partly because drawing in code is much faster. I know that for a couple of labels it wouldn't make a huge difference, but eventually I'll want to generalise this to more complex cells.)
Currently I'm struggling with the delete button animation: how to animate the cell shrinking as the delete button slides in.
Firstly, I am drawing in a custom subview of the cell's contentView. Everything is drawn in that one subview.
I am setting the subview's size by catching layoutSubviews on the cell itself, and doing:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect b = [self.contentView bounds];
[subcontentView setFrame:b];
}
I'm doing this rather than just setting an autoresizing mask because it seemed more reliable in testing, but I can use the autoresizing mask approach in testing if needed.
Now, the default thing that happens when someone hits the minus is the view gets squished.
I can avoid that by, when setting up my cell, calling
subcontentView.contentMode = UIViewContentModeRedraw;
That gives me the correct end result (my custom view redraws with the new size, and is laid out properly, like the first image I posted), but the animation of the transition is unpleasant: it looks like the cell stretches and shrinks back to size.
I know why the animation is working like that: Core Animation doesn't ask your view to redraw for each frame, it gets it to redraw for the end position of the animation and then interpolates to find the middle bits.
Another solution is to do
subcontentView.contentMode = UIViewContentModeLeft;
That just draws the delete button over my cell, so it covers part of it.
If I also implement
- (void) didTransitionToState:(UITableViewCellStateMask)state
{
[self setNeedsDisplay];
}
then once the delete button has slid in the cell 'jumps' to the correct size. That way there's no nice slidey animation, but at least I get the correct result at the end.
I guess I could run my own animation in parallel with the delete button appearing, temporarily creating another view with a copy of the image of my view in the old size, setting mine to the new size, and fading between them — that way there would be a nice cross fade instead of a sharp jump. Anyone use such a technique?
Now, you might ask why I can't use the contentStretch property and give it a region to resize. The problem with that is I'm making something to be reasonably generic, so it's not always going to be possible. In this particular example it'd work, but a more complex cell may not.
So, my question (after all of this background) is: what do you do in this situation? Does anyone have the animating delete button working for custom drawn cells? If not, what's the best compromise?
This worked for me finally. in subclass of UITableViewCell
subDrawContentView.contentMode = UIViewContentModeLeft;
overide layout subviews method
- (void)layoutSubviews {
CGRect b = [subDrawContentView bounds];
b.size.width = (!self.showingDeleteConfirmation) ? 320 : 300;
[subDrawContentView setFrame:b];
[subDrawContentView setNeedsDisplay];
[super layoutSubviews];
}
So I will paste the code first and then I will explain:
-(void)startCancelAnimation{
[cancelButton setAlpha:0.0f];
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
cancelButton.hidden=NO;
[UIView animateWithDuration:0.4
animations:^(void){
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, 159.0, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, 159.0, text.frame.size.height)];
[cancelButton setFrame:CGRectMake(244., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:1.0f];
} ];
}
-(void)stopCancelAnimation{
[UIView animateWithDuration:0.4
animations:^(void){
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:0.0f];
}completion:^(BOOL completion){
cancelButton.hidden=YES;
[cancelButton setAlpha:1.0f];
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, DEFAULT_WIDTH_PROGRESS, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, DEFAULT_WIDTH_TEXT, text.frame.size.height)];
}
];
}
-(void)decideAnimation{
if([cancelButton isHidden]){
[self startCancelAnimation];
}
else{
[self stopCancelAnimation];
}
}
So what I have there is a button that looks like this:
I have an IBOutlet to it. And what I am doing is resizing a UIProgressView and a UITextField (you can resize whatever you want). As for the code is pretty simple, but if you need any help to understand what's going on, please ask. Also, don't forget to add the Swip Gesture to the UITableView... Like this:
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipe:)];
gesture.numberOfTouchesRequired=1;
gesture.direction = UISwipeGestureRecognizerDirectionRight;
[table addGestureRecognizer:gesture];
[gesture release];
Finally the method that does it all:
-(void)didSwipe:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
//Get the cell of the swipe...
CGPoint swipeLocation = [gestureRecognizer locationInView:self.table];
NSIndexPath *swipedIndexPath = [self.table indexPathForRowAtPoint:swipeLocation];
UITableViewCell* swipedCell = [self.table cellForRowAtIndexPath:swipedIndexPath];
//Make sure its a cell with content and not an empty one.
if([swipedCell isKindOfClass:[AMUploadsTableViewCell class]]){
// It will start the animation here
[(AMUploadsTableViewCell*)swipedCell decideAnimation];
// Do what you want :)
}
}
}
So as you can see the whole animation is created manually, so you can control exactly what you want. :)