Image sizes not working with Page Control & ScrollView? - objective-c

I'm trying to implement page control and scroll view using an image array, and for some reason, my images are shifting around when swiping to the next page. It almost seems like the images are too big for the scroll view, but making them smaller doesn't seem to work. Is there a way to make it so that the ScrollView doesn't "scroll" vertically? See code below.
SSViewController.h
#import <UIKit/UIKit.h>
#interface SSViewController : UIViewController <UIScrollViewDelegate>
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#property (strong, nonatomic) IBOutlet UIPageControl *pageControl;
#property (nonatomic, strong) NSArray *imageArray;
SSViewController.m
-(void)viewDidAppear:(BOOL)animated
{
imageArray = [[NSArray alloc] initWithObjects:#"first.png", #"second.png", nil];
for (int i = 0; i < [imageArray count]; i++) {
//We'll create an imageView object in every 'page' of our scrollView.
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:i]];
size:imageView.clipsToBounds = YES;
imageView.contentMode = UIViewContentModeScaleAspectFill;
[self.scrollView addSubview:imageView];
}
//Set the content size of our scrollview according to the total width of our imageView objects.
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [imageArray count], scrollView.frame.size.height);
}
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
// Update the page when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, 0);
}

I think good idea will be to shift all your allocaton code (espacially all code define frames) to viewWillAppear method, because in viewDidLoad some frame wouldn't be already set.

Related

Objactive C UISlider changing minimum track image only

Im trying to setMinimumTrackImage of the slider using an image with CAGradientLayer, lets say using blue and red colors.
what happens is that the full track gets the gradient color, its starts with red, and sliding the Thumb to the right reveals the blue.
I want the color to start from red to blue up to the Thumb, ands "stretch" as the Thumb moves.
any ideas ? I though about setting the slider.maximumValue = slider...width
and change the gradient image as I listen to the slider value change but it didn't work
I don't think you'll be successful trying to set the min track image.
Options are a completely custom slider...
or
Set the min track image to a clear image, add an imageView with the gradient image behind the slider, stretch the frame of the imageView to match the thumb movement.
Here's an example:
and the code (just a starting point... would be much better to wrap it into a subclass):
SliderTestViewController.h
//
// SliderTestViewController.h
//
// Created by Don Mag on 10/31/19.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface SliderTestViewController : UIViewController
#end
NS_ASSUME_NONNULL_END
SliderTestViewController.m
//
// SliderTestViewController.m
//
// Created by Don Mag on 10/31/19.
//
#import "SliderTestViewController.h"
#import "UIImage+Utils.h"
#interface SliderTestViewController ()
#property (strong, nonatomic) UIImageView *theFakeSliderTrackImageView;
#property (strong, nonatomic) UISlider *theSlider;
#property (strong, nonatomic) NSLayoutConstraint *imgWidthConstraint;
#end
#implementation SliderTestViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// instantiate a slider
_theSlider = [UISlider new];
// instantiate an image view to use as our custom / fake "left side" of the slider track
_theFakeSliderTrackImageView = [UIImageView new];
// we want the image to stretch
_theFakeSliderTrackImageView.contentMode = UIViewContentModeScaleToFill;
// create a horizontal gradient image to use for our "left side" of the slider track
// the image will be stretched... using a width of 128 seems reasonable
UIImage *gradImg = [UIImage gradientImageWithSize:CGSizeMake(128.0, 4.0) startColor:[UIColor blueColor] endColor:[UIColor redColor] startPoint:CGPointMake(0.0, 0.0) endPoint:CGPointMake(1.0, 0.0)];
// set the gradient image to our image view
_theFakeSliderTrackImageView.image = gradImg;
// create a clear image to use for the slider's min track image
UIImage *clearImg = [UIImage imageWithColor:[UIColor clearColor] size:CGSizeMake(1.0, 1.0)];
// set min track image to clear image
[_theSlider setMinimumTrackImage:clearImg forState:UIControlStateNormal];
// set max track image if desired
// [_theSlider setMaximumTrackImage:anImage forState:UIControlStateNormal];
_theFakeSliderTrackImageView.translatesAutoresizingMaskIntoConstraints = NO;
_theSlider.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_theFakeSliderTrackImageView];
[self.view addSubview:_theSlider];
[NSLayoutConstraint activateConstraints:#[
// constrain the slider centerY with 20-pts leading / trailing
[_theSlider.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
[_theSlider.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0],
[_theSlider.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20.0],
// constrain image view centerY to slider centerY
[_theFakeSliderTrackImageView.centerYAnchor constraintEqualToAnchor:_theSlider.centerYAnchor constant:0.0],
// constrain image view leading to slider leading
[_theFakeSliderTrackImageView.leadingAnchor constraintEqualToAnchor:_theSlider.leadingAnchor constant:0.0],
// image view height to 5-pts (adjust as desired)
[_theFakeSliderTrackImageView.heightAnchor constraintEqualToConstant:5.0],
]];
// init imageView width constraint to 0.0
_imgWidthConstraint = [_theFakeSliderTrackImageView.widthAnchor constraintEqualToConstant:0.0];
_imgWidthConstraint.active = YES;
[_theSlider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self updateSliderGradientImage];
}
- (void)updateSliderGradientImage {
// set "fake track" imageView width to origin.x of thumb rect (plus 2 for good measure)
CGRect trackRect = [_theSlider trackRectForBounds:_theSlider.bounds];
CGRect thumbRect = [_theSlider thumbRectForBounds:_theSlider.bounds trackRect:trackRect value:_theSlider.value];
_imgWidthConstraint.constant = thumbRect.origin.x + 2;
}
- (void)sliderChanged:(id)sender {
[self updateSliderGradientImage];
}
#end
UIImage+Utils.h
//
// UIImage+Utils.h
//
// Created by Don Mag on 10/31/19.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface UIImage (Utils)
+ (nullable UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size;
+ (nullable UIImage *)gradientImageWithSize:(CGSize)size startColor:(UIColor *)startColor endColor:(UIColor *)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
#end
NS_ASSUME_NONNULL_END
UIImage+Utils.m
//
// UIImage+Utils.m
//
// Created by Don Mag on 10/31/19.
//
#import "UIImage+Utils.h"
#implementation UIImage (Utils)
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
if (!color || size.height < 1 || size.width < 1)
return nil;
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull context) {
[color setFill];
[context fillRect:renderer.format.bounds];
}];
return image;
}
+ (UIImage *)gradientImageWithSize:(CGSize)size startColor:(UIColor *)startColor endColor:(UIColor *)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
if (!startColor || !endColor)
return nil;
CFArrayRef colors = (__bridge CFArrayRef) [NSArray arrayWithObjects:
(id)startColor.CGColor,
(id)endColor.CGColor,
nil];
CGGradientRef g = CGGradientCreateWithColors(nil, colors, nil);
startPoint.x *= size.width;
startPoint.y *= size.height;
endPoint.x *= size.width;
endPoint.y *= size.height;
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
CGContextDrawLinearGradient(rendererContext.CGContext, g, startPoint, endPoint, kCGGradientDrawsAfterEndLocation);
}];
return gradientImage;
}
#end

How to pin width of subviews relative?

I have one view controller with two subviews.
I would like to pin both of the subviews to have the same relative width.
Like this:
Thanks!
You need to pin subviews to top, left, right sides. Also set equal width property.
You should look at this tutorial, there is a good example of equal width containers.
Here is a simple version of doing the same thing with code,
#interface ViewController ()
#property (nonatomic, weak) UIView *view1;
#property (nonatomic, weak) UIView *view2;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self prepareView];
[self setupConstraints];
}
- (void)prepareView
{
UIView *view1 = [self createView];
UIView *view2 = [self createView];
[self.view addSubview:view1];
[self.view addSubview:view2];
self.view1 = view1;
self.view2 = view2;
}
- (void)setupConstraints
{
NSDictionary *views = #{
#"view1": self.view1,
#"view2": self.view2
};
NSString *horizontalFormat = #"H:|[view1][view2(==view1)]|";
NSString *verticalFormat = #"V:|[view1]|";
NSArray *horizontalConstraints;
NSArray *verticalConstraints;
horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:horizontalFormat
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:views];
verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalFormat
options:0
metrics:nil
views:views];
[self.view addConstraints:horizontalConstraints];
[self.view addConstraints:verticalConstraints];
}
- (UIView *)createView
{
UIView *view = [[UIView alloc ] initWithFrame:CGRectZero];
view.translatesAutoresizingMaskIntoConstraints = NO;
view.backgroundColor = [self randomColor];
return view;
}
- (UIColor *)randomColor
{
float red = arc4random_uniform(255) / 255.0;
float green = arc4random_uniform(255) / 255.0;
float blue = arc4random_uniform(255) / 255.0;
return [UIColor colorWithRed:red
green:green
blue:blue
alpha:1.0];
}
#end
The horizontalConstraint's options pin both the views top and bottom, while the format string also says that both the views has the same width. We have the first view pinned to the left edge, second view pinned to the right edge and both of them are equal, and also their top and bottom are pinned. Now, we need to tell the view that one of them is pinned to the top edge of the superView and bottom edge of the superView, which the verticalFormat describes. Now, that we have the views with equal widths, their top is pinned to superView's top and bottom to superView's bottom, the subviews will have layout as you have described. It would be quite easy to setup the constraints in storyboard knowing the above details.
You can also look at my previous answer which preserves the views position on rotation IOS AutoLayout Change possition on Rotation.

UIPageControl doesn't properly react to taps

In my TestApp, I created a class "Carousel" which should let me create a swipe menu with a UIPageControl in an easy way (by simply creating an instance of the class Carousel).
Carousel is a subclass of UIView
At init, it creates an UIView, containing UIScrollView, UIPageControl
I can add further UIViews to the scroll view
I don't know if this is the proper way to do it, but my example worked quite well in my TestApp. Swiping between pages works perfectly and the display of the current page in the UIPageControl is correct.
If there were not one single problem: The UIPageControl sometimes reacts to clicks/taps (I only tested in Simulator so far!), sometimes it doesn't. Let's say most of the time it doesn't. I couldn't find out yet when it does, for me it's just random...
As you can see below, I added
[pageControl addTarget:self action:#selector(pageChange:) forControlEvents:UIControlEventValueChanged];
to my code. I thought this would do the proper handling of taps? But unfortunately, pageChange doesn't get always called (so the value of the UIPageControl doesn't change every time I click).
I would appreciate any input on this because I couldn't find any solution on this yet.
This is what I have so far:
Carousel.h
#import <UIKit/UIKit.h>
#interface Carousel : UIView {
UIScrollView *scrollView;
UIPageControl *pageControl;
BOOL pageControlBeingUsed;
}
- (void)addView:(UIView *)view;
- (void)setTotalPages:(NSUInteger)pages;
- (void)setCurrentPage:(NSUInteger)current;
- (void)createPageControlAt:(CGRect)cg;
- (void)createScrollViewAt:(CGRect)cg;
#end
Carousel.m
#import "Carousel.h"
#implementation Carousel
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Create a scroll view
scrollView = [[UIScrollView alloc] init];
[self addSubview:scrollView];
scrollView.delegate = (id) self;
// Init Page Control
pageControl = [[UIPageControl alloc] init];
[pageControl addTarget:self action:#selector(pageChange:) forControlEvents:UIControlEventValueChanged];
[self addSubview:pageControl];
}
return self;
}
- (IBAction)pageChange:(id)sender {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * pageControl.currentPage;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:TRUE];
NSLog(#"%i", pageControl.currentPage);
}
- (void)addView:(UIView *)view {
[scrollView addSubview:view];
}
- (void)createPageControlAt:(CGRect)cg {
pageControl.frame = cg;
}
- (void)setTotalPages:(NSUInteger)pages {
pageControl.numberOfPages = pages;
}
- (void)setCurrentPage:(NSUInteger)current {
pageControl.currentPage = current;
}
- (void)createScrollViewAt:(CGRect)cg {
[scrollView setPagingEnabled:TRUE];
scrollView.frame = cg;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*pageControl.numberOfPages, scrollView.frame.size.height);
[scrollView setShowsHorizontalScrollIndicator:FALSE];
[scrollView setShowsVerticalScrollIndicator:FALSE];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float frac = scrollView.contentOffset.x / scrollView.frame.size.width;
NSUInteger page = lround(frac);
pageControl.currentPage = page;
}
#end
ViewController.m (somewhere in viewDidLoad)
Carousel *carousel = [[Carousel alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
for (int i=0; i<5; i++) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(i * 320, 0, 320, 420)];
UIColor *color;
if(i%3==0) color = [UIColor blueColor];
else if(i%3==1) color = [UIColor redColor];
else color = [UIColor purpleColor];
view.backgroundColor = color;
[carousel addView:view];
view = nil;
}
[carousel setTotalPages:5];
[carousel setCurrentPage:0];
[carousel createPageControlAt:CGRectMake(0,420,320,40)];
[carousel createScrollViewAt:CGRectMake(0,0,320,420)];
Your code is correct. Most likely the frame of your pageControl is pretty small, so theres not a lot of area to look for touch events. You would need to increase the size of the height of pageControl in order to make sure taps are recognized all of the time.

Zoom with Zoomed Buttons on a UIImageView

I have a flat image of a map in a UIImageView and its my job to add clear buttons to some pre-defined "hot spots" that represent locations of interest on the map. However, given the size of the image on the iPhone, the hotspots they have defined are very close together and would be difficult to touch / tap.
Is there a way to zoom in on an image have those buttons scale larger so that they can be pressed more easily?
Yes add the imageview inside a scroll view,
Use this code
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageview = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"yourimage"]];
self.imageView = imageview;
[imageview release];
scrollView.contentSize = CGSizeMake(imageView.frame.size.width, imageView.frame.size.height);
scrollView.maximumZoomScale = 4.0;
scrollView.minimumZoomScale = 0.75;
scrollView.clipsToBounds = YES;
scrollView.delegate = self;
[scrollView addSubview:imageView];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return imageView;
}
In the .h file you would have
#property (nonatomic, retain) UIScrollView *scrollView;
#property (nonatomic, retain) UIImageView *imageView;
For more info please check StackOverFlow on how to add to use UIScrollView
such as Adding UIImageViews to UIScrollView

Objective-C: An array of UIButtons

If I have a large number of buttons set up in Interface Builder, how can I place them into an array via code?
For example, if I have three buttons and in the interface file I define them:
#property (nonatomic, retain) IBOutlet UIButton *button1;
#property (nonatomic, retain) IBOutlet UIButton *button2;
#property (nonatomic, retain) IBOutlet UIButton *button3;
If I want to rename each of these buttons to say "Hello", I want to be able to lump them into an array and then use a function such as:
for (int i = 0; i < someArray.count; i++)
{
someArray[i].text = #"Hello";
}
Could someone please provide information on how this is achieved (if it's possible)?
Thanks
Another approach is to wire the buttons up to an NSArray in Interface Builder.
#property (nonatomic, retain) IBOutletCollection(UIButton) NSArray *buttons;
The following code will add 5 buttons, each with a new tag and also connect it to an event. The buttons will be placed side by side with a 5px padding:
If you plan on accessing the button outside of the for{} scope below, you can define your button in your header, otherwise, you can define it inside the scope. UIButton * settButton;
.m
CGFloat staticX = 5; // Static X for all buttons.
CGFloat staticWidth = 60; // Static Width for all Buttons.
CGFloat staticHeight = 56; // Static Height for all buttons.
CGFloat staticPadding = 5; // Padding to add between each button.
for (int i = 0; i < 5; i++)
{
settButton = [UIButton buttonWithType:UIButtonTypeCustom];
[settButton setTag:i];
[settButton setFrame:CGRectMake((staticX + (i * (staticHeight + staticPadding))),5,staticWidth,staticHeight)];
[settButton setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%i.png",i]] forState:UIControlStateNormal];
[settButton addTarget:self action:#selector(buttonEvent:) forControlEvents:UIControlEventTouchDown];
[settButtonView addSubview: settButton];
}
[self.view addSubview: settButtonView];
Assuming the buttons in IB are correctly wired to the IBOutlet properties then its as straightforward as:
NSArray *buttonArray = [[NSArray alloc] initWithObjects:button1,button2,button3,nil];
Since you created the buttons directly in Interface Builder, can't you just set them directly in code?
A more general solution would be something like this...if I assume two things: that they are all subviews of the UIViewController's view and that these UIButtons are the only UIButtons in that view, you can do something like this:
for (UIView *aView in [self.view subviews]) {
if ([aView isKindOfClass:[UIButton class]) {
UIButton *aButton = (UIButton *) aButton;
[aButton setTitle:#"Hello" forState:UIControlStateNormal];
}
}
I think that the easiest is to create a single button that you will init and add to a mutable array:
in *.m:
UIButton *buttonChar;
in your function:
buttonArray = [[NSMutableArray alloc] init];
for (int i=0; i<lenghtWord; i++) {
buttonChar = [[UIButton alloc] initWithFrame:CGRectMake(.2*size.height+(float)i*charFontSize, .5*size.width, charFontSize, charFontSize)];
buttonChar.tag = i;
[buttonChar setTitleColor:[UIColor blackColor] forState:(UIControlStateNormal)];
[buttonChar setTitle:[[NSString alloc] formatWithString:#"Button %d",i] forState:UIControlStateNormal];
buttonChar.titleLabel.font = [UIFont boldSystemFontOfSize:15];
buttonChar.titleLabel.textAlignment = UITextAlignmentCenter;
buttonChar.layer.cornerRadius = 5;
buttonChar.layer.masksToBounds = YES;
buttonChar.layer.borderWidth = 1;
buttonChar.layer.borderColor = [[UIColor blackColor] CGColor];
[buttonChar setBackgroundColor:[UIColor whiteColor]];
[buttonChar addTarget:self action:#selector(buttonAction:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[buttonArray addObject:buttonChar];
}
buttons are available through:
[[buttonArray objectAtIndex:j] setTitle:#" " forState:UIControlStateNormal];
Since the tag option is also filled, I can always tell what button from the array was touched if it was displayed (buttonAction:withEvent:)
Cheers