I have a situation like this:
If I click on left foot button I want that my view in the second controller show a left foot image, and if I click on right button the view should show right foot image.
In the first controller I wrote this to select right or left:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
RSViewController *vc = [segue destinationViewController];
if ([[segue identifier] isEqualToString:#"Left-Foot"])
[vc setLeft:YES];
else
[vc setLeft:NO];
}
In the second controller I have a method which set a boolean value
- (void)setLeft:(BOOL)left
{
if (left) {
_left = left;
NSLog(#"LEFT UPDATED IN RSVIEWCONTROLLER");
}else {
_left = left;
NSLog(#"RIGHT UPDATED IN RSVIEWCONTROLLER");
}
}
and in the same controller I create my view in viewdidload with my one init:
- (void)viewDidLoad
{
NSLog(#"meeeea");
[super viewDidLoad];
CGRect rect = CGRectMake(0, 0, 100, 100);
self.animatedCircleView = [[RSAnimatedCircleView alloc] initWithFrameFoot:rect foot:YES];
NSLog(#"Viewdidnoload");
}
and in my view (small blue rectangle in the picture):
- (void)setup:(BOOL)left
{
self.leftFoot = left;
}
- (void)setLeftFoot:(BOOL)leftFoot
{
_leftFoot = leftFoot;
}
- (void)awakeFromNib
{
[self setup:_leftFoot];
}
- (id)initWithFrameFoot:(CGRect)frame foot:(BOOL)left
{
self = [super initWithFrame:frame];
[self setup:left];
self.leftFoot = left;
if (self.leftFoot)
NSLog(#"Left == TRUE");
return self;
}
- (RSFootView *)footView
{
CGRect rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
if (self.leftFoot) {
NSLog(#"Now left is yes");
}else {
NSLog(#"left is no");
}
_footView = [[RSFootView alloc] initWithFrame:rect withLeftFoot:self.leftFoot];
[_footView setBackgroundColor:[UIColor clearColor]];
return _footView;
}
- (void)drawRect:(CGRect)rect
{
if (self.leftFoot)
NSLog(#"left ada");
else
NSLog(#"right masd");
CGFloat x = self.bounds.size.height;
CGFloat y = self.bounds.size.width;
CGFloat radius = x/2 - 15;
CGPoint center = CGPointMake(x/2, y/2);
CGFloat startAngle = DEGREES_TO_RADIANS(START_ANGLE);
CGFloat endAngle = DEGREES_TO_RADIANS(END_ANGLE);
// Drawing the sector
UIBezierPath *sector = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
sector.lineWidth = 4;
[[UIColor grayColor] setStroke];
[sector stroke];
[self setBackgroundColor:[UIColor clearColor]];
[sector addClip];
[self addSubview:self.footView];
}
in the method "drawRect" the leftfoot value is always false. I do not know why is like this... can anyone help me?
Thank you
You are setting your view to "left" each time in the following line.
self.animatedCircleView = [[RSAnimatedCircleView alloc]
initWithFrameFoot:rect foot:YES];
This is a typical error that becomes very probable when not giving your variables and methods understandable and intuitive names. Expect this type of error to occur frequently if you do not mend your ways.
I would give your view an enum property that states explicitly what foot it is:
typedef enum {LeftFoot, RightFoot} FootType;
#property (strong, nonatomic) FootType footType;
Also, you should revise your method names. I would completely do away with the initWithFrameFoot method and just set a default foot in your override for initWithFrame. (Leaving it at 0 amounts to LeftFoot.) With #synthesize you also do not need a setter or getter. Delete them.
If you want a dedicated initializer, make sure the variable passed are actually preceded by the proper words.
-(id) initWithFrame:(CGRect)rect andFootType:(FootType)footType {
self = [super initWithFrame:rect];
if (self) {
_footType = footType;
}
return self;
}
After using #synthesize you can set the correct foot in prepareForSegue:
footViewController.footType = rightFoot;
Related
I've tried all found suggested solutions but ended up with this as the closest:
The target is to have custom color for:
complete header background (e.g. green)
text (e.g. white)
sort control color (e.g. white)
Currently I can only set the interior bg and text color properly while leaving the header borders and sort controls in default white color.
I use the approach of custom NCTableHeaderCell.
// <C> changing the bgColor doesn't work this way
[self.tv.headerView setWantsLayer:YES];
self.tv.headerView.layer.backgroundColor = NSColor.redColor.CGColor;
for (NSTableColumn *tc in self.tv.tableColumns) {
// <C> this only helps to change the header text color
tc.headerCell = [[NCTableHeaderCell_customizable alloc]initTextCell:#"Hdr"];
// <C> this changes the bgColor of the area of the headerCell label text (the interior) but it leaves border and sort controls in white color;
tc.headerCell.drawsBackground = YES;
tc.headerCell.backgroundColor = NSColor.greenColor;
// <C> changing the textColor doesn't work this way
// <SOLUTION> use NCTableHeaderCell_customizable as done above;
tc.headerCell.textColor = NSColor.redColor;
}
My custom class look like this:
#implementation NCTableHeaderCell_customizable
// <C> this works as expected
- (NSColor *) textColor
{
return NSColor.whiteColor;
}
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//- (NSColor *) backgroundColor
//{
// return NSColor.redColor;
//}
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//self.backgroundColor = NSColor.orangeColor;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// [NSColor.redColor set];
// NSRectFillUsingOperation(cellFrame, NSCompositingOperationSourceOver);
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawFocusRingMaskWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawFocusRingMaskWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[super drawSortIndicatorWithFrame:cellFrame inView:controlView ascending:ascending priority:priority];
//NSTableHeaderView *v = (NSTableHeaderView *)controlView;
}
I'm quite close to the solution but I don't know how to draw correctly the custom header cell to archive the goal.
Create the NSTableHeaderCell like below and play with the header cell's frame sizes and origins:
#import <Cocoa/Cocoa.h>
#interface CustomTableHeaderCell : NSTableHeaderCell {
NSMutableDictionary *attrs;
}
#import "CustomTableHeaderCell.h"
#implementation CustomTableHeaderCell
- (id)initTextCell:(NSString *)text
{
if (self = [super initTextCell:text]) {
if (text == nil || [text isEqualToString:#""]) {
[self setTitle:#"One"];
}
/* old_
self.textColor = [NSColor blackColor];
*/
// new_
self.textColor = [NSColor whiteColor];
attrs = [[NSMutableDictionary dictionaryWithDictionary:
[[self attributedStringValue]
attributesAtIndex:0
effectiveRange:NULL]]
mutableCopy];
// self.font = [NSFont fontWithName:appleeH8GhKr0 size:12];
return self;
}
return nil;
}
- (void)drawWithFrame:(NSRect)cellFrame
highlighted:(BOOL)isHighlighted
inView:(NSView *)view
{
CGRect fillRect, borderRect;
CGRectDivide(NSRectToCGRect(cellFrame), &borderRect, &fillRect, 1.0, CGRectMaxYEdge);
// sets the origin and frame for header's title
// fillRect.size.height = 25;
if (fillRect.origin.x == 0)
{
// fillRect.origin.y += 5;
// for adding left margin to first column title try to add spaces in text of header title
// try to play with the numbers of fillRect rect
}
else if (fillRect.origin.x == 239)
{
// fillRect.size.width -= 80;
}
// setting the background color of tableview's header view
NSGradient *gradient = [[NSGradient alloc]
initWithStartingColor:[NSColor greenColor]
endingColor:[NSColor greenColor]];
[gradient drawInRect:NSRectFromCGRect(fillRect) angle:90.0];
[self drawInteriorWithFrame:NSRectFromCGRect(CGRectInset(fillRect, 0.0, 1.0)) inView:view];
}
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)frame
{
// super would normally draw text at the top of the cell
NSInteger offsetY = floor((NSHeight(frame) -
([[self font] ascender] - [[self font] descender])) / 2);
return NSInsetRect(frame, 20, offsetY);
}
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
[super drawInteriorWithFrame:[self adjustedFrameToVerticallyCenterText:cellFrame]
inView:controlView];
}
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:NO inView:view];
}
- (void)highlight:(BOOL)isHighlighted
withFrame:(NSRect)cellFrame
inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:isHighlighted inView:view];
}
Set the header cell for column like below:
NSArray *columns = nil;
if(self.tblVc)
columns = [self.tblVc tableColumns];
NSEnumerator *cols = [columns objectEnumerator];
NSTableColumn *col = nil;
CustomTableHeaderCell *headerCell;
while (col = [cols nextObject]) {
NSString *key = [[col headerCell] stringValue];
headerCell = [[CustomTableHeaderCell alloc]
initTextCell:key];
NSRect rectHeader =self.tblVc.headerView.frame;
rectHeader.size.height = 25;
self.tblVc.headerView.frame=rectHeader;
[col setHeaderCell:headerCell];
}
and use disclosure button on top of table view's header like below:
It will show the results like this:
I don't find other ways to do it, just have to draw everything. Hope it's helpful.
CGRect outCellFrame;
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
outCellFrame = cellFrame;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
-(NSRect *) outer:(NSRect)rect fromInner: (NSRect)innerRect {
NSRect * list = (NSRect *) malloc(sizeof(rect) * 4);
NSRect rem;
NSDivideRect(rect, &list[0], &rem, innerRect.origin.x - rect.origin.x, NSRectEdgeMinX);
NSDivideRect(rect, &list[1], &rem, - innerRect.origin.x - innerRect.size.width + rect.origin.x + rect.size.width , NSRectEdgeMaxX);
NSDivideRect(rect, &list[2], &rem, innerRect.origin.y - rect.origin.y, NSRectEdgeMinY);
NSDivideRect(rect, &list[3], &rem, -innerRect.origin.y - innerRect.size.height + rect.origin.y + rect.size.height , NSRectEdgeMaxY);
return list;
}
-(void) updateBackground:(CGRect) cellFrame and:(CGRect) innerCellFrame{
[self.backgroundColor set];
NSRect * list = [self outer:cellFrame fromInner:innerCellFrame];
NSRectFillListUsingOperation(list, 4, NSCompositingOperationSourceOver);
free(list);}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[self updateBackground:outCellFrame and:cellFrame];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[self.backgroundColor set];
NSImage * image = ascending ? [NSImage imageNamed:#"NSDescendingSortIndicator"]:[NSImage imageNamed:#"NSAscendingSortIndicator"] ;
// use your image here. If you need to change color, try to make a colored templated image here.
CGRect frame = [self sortIndicatorRectForBounds:cellFrame];
[self.backgroundColor set];
CGRect res = NSMakeRect(frame.origin.x, cellFrame.origin.y, cellFrame.size.width - frame.origin.x, cellFrame.size.height);
NSRectFillUsingOperation(res , NSCompositingOperationSourceOver);
[NSColor.blueColor setFill];
[NSColor.blueColor setStroke];
[image drawInRect: [self sortIndicatorRectForBounds:frame]];
}
Thanks guys for your help. I've ended up with this simple solution which also solves the sort indicators. I've gave up on custom NSHeaderCell and resolved everything in single custom NSTableHeaderRowView method:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
// Row: Fill the background
//
[NSColor.whiteColor setFill];
const CGRect headerRect = CGRectMake(0.0, 1.0, self.bounds.size.width, self.bounds.size.height-1.0);
[[NSBezierPath bezierPathWithRect:headerRect] fill];
// Columns
//
for (NSUInteger i = 0; i < self.tableView.numberOfColumns; ++i) {
NSRect rect = [self headerRectOfColumn:i];
// separator on left
//
if (i != 0) {
[[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.35] setFill];
[[NSBezierPath bezierPathWithRect:NSMakeRect(rect.origin.x, rect.origin.y+4.0, 1.0, rect.size.height-8.0)] fill];
}
NSTableColumn *tableColumn = self.tableView.tableColumns[i];
NSSortDescriptor *col_sd = tableColumn.sortDescriptorPrototype;
NSTableHeaderCell *tableHeaderCell = tableColumn.headerCell;
// text
//
NSString *columnText = tableHeaderCell.stringValue;
[columnText drawInRect:NSInsetRect(rect, 5.0, 4.0) withAttributes:nil];
// sort indicator
//
for (NSInteger priority = 0; priority < self.tableView.sortDescriptors.count; ++priority) {
NSSortDescriptor *sortDesciptor = self.tableView.sortDescriptors[priority];
// <C> there is no way to get column from sortDesciptor so I use this trick comparing sortDesciptor with current column.sortDescriptorPrototype;
// <C> not sure why sel_isEqual() dosn't work so I compare its string representation;
if ([NSStringFromSelector(sortDesciptor.selector) isEqualToString:NSStringFromSelector(col_sd.selector)] == YES && [sortDesciptor.key isEqualToString:col_sd.key] == YES) {
SDEBUG(LEVEL_DEBUG, #"sort-hdr", #"MATCH: sel=%#", NSStringFromSelector(sortDesciptor.selector));
// <C> default implementation draws indicator ONLY for priority 0; Otherwise the indicator would have to graphically show the prio;
// <C> it is a support for multi-column sorting;
// <REF> shall you need it: https://stackoverflow.com/questions/46611972/sorting-a-viewbased-tableview-with-multiple-sort-descriptors-doesnt-work
[tableHeaderCell drawSortIndicatorWithFrame:rect inView:self ascending:sortDesciptor.ascending priority:priority];
}
}
}
[NSGraphicsContext restoreGraphicsState];
}
I have a class called MNTRectangle which is a subclass of UIImage. I have overridden the drawRect method of this class to draw a border on the image (using its frame). I have it so when the user starts a panning/dragging gesture on a class called DocumentView (subclass of UIView) it adds an instance of MNTRectangle as a subview to the instance of DocumentView. As the user continues to drag the MNTRectangle is resized. The problem is that the MNTRectangle is appearing as solid black in the end, I have tried clearing the graphics context as well as saving the context before drawing the border and restoring the context after drawing the border. No matter what I seem to try I cannot get the MNTRectangle to clear and just show the border on resize.
Here is the code for my MNTRectangle class:
#implementation MNTRectangle
- (id)init
{
self = [super init];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setup];
}
return self;
}
- (void)setup
{
[self setBackgroundColor:[UIColor clearColor]];
}
- (void)drawRect:(CGRect)rect
{
// Get graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
// CGContextSaveGState(context);
// CGContextClearRect(context, rect);
// Draw border
CGContextSetLineWidth(context, 4.0);
[[UIColor blackColor] setStroke];
CGContextStrokeRect(context, rect);
// CGContextRestoreGState(context);
}
Here is the code in DocumentView for the panning/dragging handling on the UIView:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan)
{
_startPanPoint = [sender locationInView:self];
MNTRectangle *rectangle = [[MNTRectangle alloc] initWithFrame:CGRectMake(_startPanPoint.x, _startPanPoint.y, 1, 1)];
[_objects addObject:rectangle];
_currentPanObject = rectangle;
[self addSubview:rectangle];
}
else if ([sender state] == UIGestureRecognizerStateChanged)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
}
else if ([sender state] == UIGestureRecognizerStateEnded)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
}
}
Any help with this would be greatly appreciated.
I finally figured it out. In my handlePan: method after I set the frame of the rectangle I was missing [rectangle setNeedsDisplay];.
Now my DocumentView code looks like this:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan)
{
_startPanPoint = [sender locationInView:self];
MNTRectangle *rectangle = [[MNTRectangle alloc] initWithFrame:CGRectMake(_startPanPoint.x, _startPanPoint.y, 1, 1)];
[_objects addObject:rectangle];
_currentPanObject = rectangle;
[self addSubview:rectangle];
}
else if ([sender state] == UIGestureRecognizerStateChanged)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
[rectangle setNeedsDisplay];
}
else if ([sender state] == UIGestureRecognizerStateEnded)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
[rectangle setNeedsDisplay];
}
}
You're gonna want to call [super drawRect:rect]
To clarify for anyone reading, calling [rectangle setNeedsDisplay]; is needed because you're overriding drawRect: and thus this subclass of UIView, MNTRectangle, needs to be told to manually redraw itself after it's internal variables have been changed.
This function is called automatically on standard objects that inherit from UIView, but since you've created a custom object, you'll need to call it manually if you have some special behavior in MNTRectangle that requires a redraw.
Further documentation here.
I've made a map with UIScrolView, I want to place other small images or buttons onto the map and have them be in a relative position on the map when you zoom in and be able to click on them whenever. So when zoomed out, a button on country A, will still be on Country A when zoomed in, and disappear out of the screen when you scroll out of the countries view whilst zoomed in. How could I go about doing this?
As i can understand, you want to place custom views on your own custom map. And you need to keep the same sizes for views, but they should move when you scroll or zoom imageView.
You have to place views to scrollView's superview and recalculate positions when you zoom or scroll:
CustomMapViewController.h:
#interface CustomMapViewController : UIViewController <UIScrollViewDelegate>
{
UIScrollView *_scrollView;
UIImageView *_mapImageView;
NSArray *_customViews;
}
CustomMapViewController.m:
#import "CustomMapViewController.h"
enum {
kAddContactButton = 1,
kInfoDarkButton,
kInfoLightButton,
kLogoImage,
};
#implementation CustomMapViewController
- (void)dealloc
{
[_scrollView release]; _scrollView = nil;
[_mapImageView release]; _mapImageView = nil;
[_customViews release]; _customViews = nil;
[super dealloc];
}
- (void) loadView
{
[super loadView];
UIImageView *mapImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map.png"]];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
scrollView.delegate = self;
scrollView.minimumZoomScale = 0.2;
scrollView.maximumZoomScale = 2.0;
[scrollView addSubview:mapImageView];
scrollView.contentSize = mapImageView.frame.size;
[self.view addSubview:scrollView];
_scrollView = scrollView;
_mapImageView = mapImageView;
// Add custom views
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn1.tag = kAddContactButton;
[self.view addSubview:btn1];
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn2.tag = kInfoDarkButton;
[self.view addSubview:btn2];
UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeInfoLight];
btn3.tag = kInfoLightButton;
[self.view addSubview:btn3];
UIImageView *image = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"logo.png"]] autorelease];
image.tag = kLogoImage;
[self.view addSubview:image];
_customViews = [[NSArray alloc] initWithObjects:btn1, btn2, btn3, image, nil];
[self _zoomToFit];
}
- (void) _zoomToFit
{
UIScrollView *scrollView = _scrollView;
CGFloat contentWidth = scrollView.contentSize.width;
CGFloat contentHeigth = scrollView.contentSize.height;
CGFloat viewWidth = scrollView.frame.size.width;
CGFloat viewHeight = scrollView.frame.size.height;
CGFloat width = viewWidth / contentWidth;
CGFloat heigth = viewHeight / contentHeigth;
CGFloat scale = MIN(width, heigth); // to fit
// CGFloat scale = MAX(width, heigth); // to fill
// May be should add something like this
if ( scale < _scrollView.minimumZoomScale ) {
_scrollView.minimumZoomScale = scale;
} else if ( scale > _scrollView.maximumZoomScale ) {
_scrollView.maximumZoomScale = scale;
}
_scrollView.zoomScale = scale;
}
////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Positions
- (void) _updatePositionForViews:(NSArray *)views
{
CGFloat scale = _scrollView.zoomScale;
CGPoint contentOffset = _scrollView.contentOffset;
for ( UIView *view in views ) {
CGPoint basePosition = [self _basePositionForView:view];
[self _updatePositionForView:view scale:scale basePosition:basePosition offset:contentOffset];
}
}
- (CGPoint) _basePositionForView:(UIView *)view
{
switch (view.tag) {
case kAddContactButton:
return CGPointMake(50.0, 50.0);
case kInfoDarkButton:
return CGPointMake(250.0, 250.0);
case kInfoLightButton:
return CGPointMake(450.0, 250.0);
case kLogoImage:
return CGPointMake(650.0, 450.0);
default:
return CGPointZero;
}
}
- (void) _updatePositionForView:(UIView *)view scale:(CGFloat)scale basePosition:(CGPoint)basePosition offset:(CGPoint)offset;
{
CGPoint position;
position.x = (basePosition.x * scale) - offset.x;
position.y = (basePosition.y * scale) - offset.y;
CGRect frame = view.frame;
frame.origin = position;
view.frame = frame;
}
//////////////////////////////////////////////////////////////////////////////////////
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
return _mapImageView;
}
#end
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.
I have a storyboard which loads loads a custom UIView. Also a sub view is added to the view in the storyboard. It worked fine until I overwrote the drawRect method of the sub view, then I just saw a black rectangle instead of the subview. Here is the code:
#import <UIKit/UIKit.h>
#import "MySubview.h"
#interface MyView : UIView
#end
#import "MyView.h"
#implementation MyView
- (void) awakeFromNib
{
CGRect frame = [self frame];
MySubview* sv = [[MySubview alloc] initWithFrame:frame];
[self addSubview:sv];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
#end
#import <UIKit/UIKit.h>
#interface MySubview : UIView
#property (retain, nonatomic) NSString* text;
#property (retain, nonatomic) UILabel* label;
#end
#import "MySubview.h"
#implementation MySubview
#synthesize text, label;
- (void)attachLabel
{
text = #"Hello";
label = [[UILabel alloc] init];
[label setText:text];
[label setFont:[UIFont fontWithName:#"Futura" size:18]];
[label setBackgroundColor:[UIColor clearColor]];
[label sizeToFit];
CGRect labelFrame = label.frame;
labelFrame.origin.x = (self.frame.size.width - labelFrame.size.width) / 2;
labelFrame.origin.y = (self.frame.size.height - labelFrame.size.height) / 2;
label.frame = labelFrame;
[self addSubview:label];
}
- (void)awakeFromNib
{
[self attachLabel];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self attachLabel];
}
return self;
}
// Works if I comment this out!
- (void)drawRect:(CGRect)rect
{
}
#end
Update - Added drawing code below:
- (void)drawRectWithRoundBorders:(CGRect)rect
{
[super drawRect:rect];
// Parameters used for drawing.
const CGFloat lineWidth = 5;
const CGFloat shadowOffset = 3;
const CGFloat shadowBlur = 4;
const CGFloat spaceToBB = 10; // Space to the bounding box of this view.
const CGFloat cornerRadii = 5;
const CGFloat lineColor[4] = { 0, 0, 0, 1 };
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetStrokeColor(ctx, lineColor);
CGContextSetShadow(ctx, CGSizeMake(shadowOffset, shadowOffset), shadowBlur);
CGRect innerRect = rect;
innerRect.size.width -= 2*spaceToBB;
innerRect.size.height -= 2*spaceToBB;
innerRect.origin.x += spaceToBB;
innerRect.origin.y += spaceToBB;
UIBezierPath *path =
[UIBezierPath bezierPathWithRoundedRect:innerRect
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(cornerRadii, cornerRadii)
];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}
- (void)drawRect:(CGRect)rect
{
[self drawRectWithRoundBorders:rect];
}
Update
It seems to work when I fill the bounding box of the sub view with some color first.
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat white[] = {1, 1, 1, 0.5};
CGContextSetFillColor(ctx, white);
CGContextAddRect(ctx, rect);
CGContextFillPath(ctx);
Just add [self setOpaque:NO] in your initWithFrame: method.
That's because your drawRect is doing nothing. You override drawRect if you want to do custom drawing. So:
If you don't want to do custom drawing then don't override drawRect.
If you do want to do custom drawing then actually do something in drawRect.