I am building an app to go alongside a web app I've built, which features a threaded comment system.
I'm wondering what the best approach to building this threaded view would be. Is there a relatively easy way to build an accordion style control?
I really like how the Alien Blue app does it, and the UI & UX is very smooth:
Does anyone have any idea how these are built?
Would making custom UIViews added as subviews be the best approach? If so, how would you implement 'collapse' style functionality?
I'd suggest creating a UIView subclass to contain each comment. It should have a toggle button to expand / collapse. On toggling open i'd set the frame size to the size of the content (plus any padding). It would contain an array of sub-comments, for each one you'd add the UIView subclasses to itself at expand time (And remove when collapsing) which would initially be collapsed (and so just appear as the toggle button). Collapsing is just the reverse, remove the subviews, set the frame to be the hight of the toggle button (plus padding)
As each comment view knows its size, you can put the whole thing in a UIScrollView with content-size set to the sum of the comment views sizes allowing scrolling regardless of expanded / contracted nature.
A partial implementation for this idea:
Comment.h
#import <Foundation/Foundation.h>
#interface Comment : NSObject {
NSMutableArray* subComments;
NSString* comment;
}
#property (nonatomic, retain) NSMutableArray* subComments;
#property (nonatomic, retain) NSString* comment;
#end
Comment.m
#import "Comment.h"
#implementation Comment
#synthesize comment, subComments;
-(id)init
{
self = [super init];
if (self)
{
subComments = [[NSMutableArray alloc] init];
}
return self;
}
#end
CommentView.h
#import <UIKit/UIKit.h>
#interface CommentView : UIView {
UIButton* toggle;
NSMutableArray* subComments;
NSString* commentText;
UITextView* comment;
BOOL expanded;
}
#property (strong, nonatomic) NSMutableArray* subComments;
#property (strong, nonatomic) NSString* commentText;
- (void) expand;
- (void) collapse;
- (void) toggleState;
#end
CommentView.m
#import "CommentView.h"
#implementation CommentView
#synthesize subComments,commentText;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
[self setBackgroundColor:[UIColor whiteColor]];
expanded = NO;
toggle = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[toggle setTitle:#"Toggle" forState:UIControlStateNormal];
[toggle addTarget:self action:#selector(toggleState) forControlEvents:UIControlEventTouchUpInside];
CGRect curentFrame = self.frame;
curentFrame.size.height = toggle.frame.size.height + 10;
[self setFrame:curentFrame];
curentFrame = toggle.frame;
curentFrame.origin.y = 5;
curentFrame.origin.x = 5;
[toggle setFrame:curentFrame];
[self addSubview:toggle];
[self collapse];
return self;
}
- (void) toggleState
{
if (expanded)
{
[self collapse];
}
else
{
[self expand];
}
}
- (void) expand
{
comment = [[UITextView alloc] init];
[comment setEditable:NO];
[comment setText:commentText];
[self addSubview:comment];
CGRect curentFrame = comment.frame;
curentFrame.size.height = comment.contentSize.height;
curentFrame.origin.x = toggle.frame.size.width + toggle.frame.origin.x + 10;
curentFrame.size.width = self.frame.size.width - curentFrame.origin.x;
curentFrame.origin.y = toggle.frame.size.height + toggle.frame.origin.y + 10;
[comment setFrame:curentFrame];
curentFrame = self.frame;
curentFrame.size.height += comment.frame.size.height + 10;
[self setFrame:curentFrame];
float height = comment.frame.origin.y + comment.frame.size.height;
for (NSObject* o in subComments)
{
CommentView* subComment = [[CommentView alloc] initWithFrame:CGRectMake(comment.frame.origin.x,height,0,self.frame.size.width)];
[self addSubview:subComment];
height += subComment.frame.size.height;
curentFrame = self.frame;
curentFrame.size.height += subComment.frame.size.height;
[self setFrame:curentFrame];
[self bringSubviewToFront:subComment];
}
expanded = YES;
}
- (void) collapse
{
for (UIView* v in [self subviews])
{
[v removeFromSuperview];
}
CGRect curentFrame = self.frame;
curentFrame.size.height = toggle.frame.size.height + 10;
[self setFrame:curentFrame];
curentFrame = toggle.frame;
curentFrame.origin.y = 5;
curentFrame.origin.x = 5;
[toggle setFrame:curentFrame];
[self addSubview:toggle];
expanded = NO;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
ViewContoller.m (Example usage)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CommentView* mainCommentView = [[CommentView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
Comment* mainComment = [[Comment alloc] init];
[mainComment setComment: #"Lorem Ipsum 1"];
Comment* sub1 = [[Comment alloc] init];
[sub1 setComment: #"Lorem Ipsum 1-1"];
Comment* sub11 = [[Comment alloc] init];
[sub11 setComment: #"Lorem Ipsum 1-1-1"];
[[sub1 subComments] addObject:sub11];
Comment* sub2 = [[Comment alloc] init];
[sub2 setComment: #"Lorem Ipsum 1-2"];
Comment* sub12 = [[Comment alloc] init];
[sub12 setComment: #"Lorem Ipsum 1-2-1"];
[[sub2 subComments] addObject:sub12];
[[mainComment subComments] addObject:sub1];
[[mainComment subComments] addObject:sub2];
[mainCommentView setCommentText:[mainComment comment]];
[mainCommentView setSubComments:[mainComment subComments]];
self.view = mainCommentView;
}
Looks like Apple has some sample code for collapsing UITableViewCells:
http://developer.apple.com/library/ios/#samplecode/TableViewUpdates/Introduction/Intro.html
There's also GCRetractableSectionController
Is something like this not just a webview with custom built HTML page displaying the comments?
Related
I'm new to iPhone development and am working on this tutorial: http://www.iosdevnotes.com/2011/03/uiscrollview-paging/#comment-25166 - but am doing it in XCode 4.3.2. The example is pretty simple and I understand the code, but for some reason the scrollViewDidScroll function isn't firing for me in my ViewController.m (this thus doesn't change the Page Control pagination icons and update it to the current page). I placed an NSLog() in this function so I can tell if it is ever activating - but in my debug console I never see this text
The code from my ViewController.h is:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate> {
UIScrollView* scrollView;
UIPageControl* pageControl;
}
#property (nonatomic, retain) IBOutlet UIScrollView* scrollView;
#property (nonatomic, retain) IBOutlet UIPageControl* pageControl;
#end
The code from my ViewController.m is:
#import "ViewController.h"
#implementation ViewController
#synthesize scrollView, pageControl;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], nil];
for (int i = 0; i < colors.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIView *subview = [[UIView alloc] initWithFrame:frame];
subview.backgroundColor = [colors objectAtIndex:i];
[self.scrollView addSubview:subview];
[subview release];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * colors.count, self.scrollView.frame.size.height);
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = colors.count;
}
//!!!!!!!!!!!!!!
//THIS IS WHERE MY PROBLEM IS -- THIS CODE DOESN'T SEEM TO EVER GET RUN EVEN WHEN I SCROLL!!!!!
//!!!!!!!!!!!!!
- (void)scrollViewDidScroll:(UIScrollView *)sender {
NSLog(#"this just fired");
// Switch the indicator 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;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
self.scrollView = nil;
self.pageControl = nil;
}
- (void)dealloc {
[scrollView release];
[pageControl release];
[super dealloc];
}
#end
Any help would be greatly appreciated. I've been going back and forth from the downloadable file they have on the tutorial to my own over and over and over... I just feel like this thing should fire when I start scrolling and have no idea why it's not... Thanks in advance!
In Interface Builder you did not connect the scroll view’s “delegate” outlet to you view controller. But you can do it programmatically.
[self.scrollView setDelegate:self];
dianz is Right, Set your delegate.
[self.scrollView setDelegate:self];
OR
self.scrollView.delegate=self;
You forgot to set your delegate:
- (void)viewDidLoad {
[super viewDidLoad];
// This is what you forgot to do, set the delegate of the scrollView.
scrollView.delegate = self;
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], nil];
for (int i = 0; i < colors.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIView *subview = [[UIView alloc] initWithFrame:frame];
subview.backgroundColor = [colors objectAtIndex:i];
[self.scrollView addSubview:subview];
[subview release];
}
Please make sure you have set the scrollview delegate to the viewcontroller you want.
You can also attach the delegate in the xib file, if the scrollview is added in the xib.
Also, don't forget to implement the scrollview delegate protocol UIScrollViewDelegate in the viewcontroller's header file.
I am another rookie in iPhone programming and I have a very basic question which I can not answer.
First of all, I am writing code in xcode 3.1.4, so that I can learn the basics on an old platform and hopefully in the near future this will allow me to create portable apps (in the sense that I will be supporting iPhone 3 as well). Unfortunately though, from what I understand, apple has stripped the documentation that came with xcode 3.1.4 and this makes it hard to guess what is the right way of doing things, since the current documentation and examples suggest routes more relevant to iOS 5 (e.g. storyboard). So, even though I appear to understand the whole concept of MVC, and although my first real toy app is actually functioning it is certainly not managing the memory correctly as the relevant dealloc methods are not used (the relevant NSLogs that I have are not shown). For the learning process I am following the course material that is online from CS193P in Winter of 2010.
To the specifics now.
I want to create a single-window app (Polygon, Assignment 3). Let's forget about the interface builder; all views/subviews will be created programmatically. For that I create a custom UIViewController. I also create a custom UIView in order to describe a custom subview of my single view that I want to present. (It is very likely that we disagree with my approach here already but for now I seriously believe that what I am trying here is conceptually right). In the app delegate (.h) I declare my mainController and this is all I really declare. The file is the following:
#import <UIKit/UIKit.h>
#import "MainViewController.h"
#interface MySimpleHelloPolyAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MainViewController * mainController;
}
#property (nonatomic, retain) IBOutlet UIWindow * window;
#property (nonatomic, assign) IBOutlet MainViewController * mainController;
#end
On runtime, I want mainController to allocate the necessary memory for the main view presented to the user and also add a subview of my custom UIView (this will be the canvas for drawing polygons). So, essentially I want to treat window as a container, the view of mainController to actually present the main view to the user, and to that view I will also have attached a subview with an instance of our custom canvas (custom view). Below is the AppDelegate.m file:
#import "MySimpleHelloPolyAppDelegate.h"
#implementation MySimpleHelloPolyAppDelegate
#synthesize window, mainController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window setBackgroundColor:[UIColor blueColor]]; // I do not want to see 'blue' on runtime
// On the creation of the MainViewController subclass I indicated that I want a nib for my new controller
// simply because this is the recommended way by apple. Apparently this should be an overkill for a simple
// one-window app, but nevertheless it should be ok.
mainController = [[MainViewController alloc] initWithNibName:nil bundle:nil];
[window addSubview:mainController.view];
[window makeKeyAndVisible];
}
- (void)dealloc {
NSLog(#"(AppDelegate): Dealloc is called");
[mainController.view removeFromSuperview];
[mainController release];
[window release];
[super dealloc];
}
#end
Let's see the MainViewController.h:
#import <UIKit/UIKit.h>
#import "Polygon.h"
#import "Canvas.h"
#interface MainViewController : UIViewController {
IBOutlet UILabel * numSidesTextLabel;
IBOutlet UILabel * numSidesValueLabel;
IBOutlet UILabel * advancedOptionsLabel;
IBOutlet UILabel * polygonNameLabel;
IBOutlet UISwitch * advancedOptionsSwitch; // Not supported yet
IBOutlet UIButton * increaseButton;
IBOutlet UIButton * decreaseButton;
IBOutlet Canvas * myCanvas;
Polygon * myPolygon;
}
#property (nonatomic, retain) IBOutlet UILabel * numSidesTextLabel;
#property (nonatomic, retain) IBOutlet UILabel * numSidesValueLabel;
#property (nonatomic, retain) IBOutlet UIButton * increaseButton;
#property (nonatomic, retain) IBOutlet UIButton * decreaseButton;
#property (nonatomic, retain) IBOutlet UIView * myCanvas;
#property (nonatomic, retain) IBOutlet Polygon * myPolygon;
- (IBAction) increase;
- (IBAction) decrease;
- (void) updateInterface;
#end
Now let's see the MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize numSidesTextLabel, numSidesValueLabel, decreaseButton, increaseButton, myCanvas, myPolygon;
- (IBAction) decrease {
[myPolygon setNumberOfSides:([myPolygon numberOfSides]-1)];
[self updateInterface];
}
- (IBAction) increase {
[myPolygon setNumberOfSides:([myPolygon numberOfSides]+1)];
[self updateInterface];
}
- (void) updateInterface {
int sides = [myPolygon numberOfSides];
UIColor * myOceanColor = [UIColor colorWithRed:0.00 green:0.333 blue:0.557 alpha:1.00];
[increaseButton setTitleColor:myOceanColor forState:UIControlStateNormal];
[increaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
[decreaseButton setTitleColor:myOceanColor forState:UIControlStateNormal];
[decreaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
numSidesValueLabel.text = [NSString stringWithFormat:#"%d", sides];
decreaseButton.enabled = YES; increaseButton.enabled = YES;
if (sides == MAX_NUM_SIDES_CONSTANT) increaseButton.enabled = NO;
else if (sides == MIN_NUM_SIDES_CONSTANT) decreaseButton.enabled = NO;
[myCanvas updateState:sides withName:[myPolygon name]];
[myCanvas setNeedsDisplay];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
}
return self;
}
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 20.0, 320.0, 460.0)];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidLoad {
CGRect tempFrame;
UIColor * oceanColor = [UIColor colorWithRed:0.000 green:0.333 blue:0.557 alpha:1.000];
// Draw the labels
tempFrame = CGRectMake(20.0, 20.0, 150.0, 20.0);
numSidesTextLabel = [[UILabel alloc] initWithFrame:tempFrame];
numSidesTextLabel.text = #"Number of sides:";
numSidesTextLabel.textAlignment = UITextAlignmentLeft;
[self.view addSubview:numSidesTextLabel];
[numSidesTextLabel release];
tempFrame = CGRectMake(160.0, 20.0, 40.0, 20.0);
numSidesValueLabel = [[UILabel alloc] initWithFrame:tempFrame];
numSidesValueLabel.text = #"-----";
numSidesValueLabel.textAlignment = UITextAlignmentLeft;
[self.view addSubview:numSidesValueLabel];
[numSidesValueLabel release];
tempFrame = CGRectMake(20.0, 125.0, 160.0, 20.0);
advancedOptionsLabel = [[UILabel alloc] initWithFrame:tempFrame];
advancedOptionsLabel.text = #"Advanced options";
advancedOptionsLabel.textAlignment = UITextAlignmentLeft;
[self.view addSubview:advancedOptionsLabel];
[advancedOptionsLabel release];
// Draw the advanced switch
tempFrame = CGRectMake(205.0, 120.0, 120.0, 60.0);
advancedOptionsSwitch = [[UISwitch alloc] initWithFrame:tempFrame];
[advancedOptionsSwitch setOn:NO animated:YES];
[self.view addSubview:advancedOptionsSwitch];
[advancedOptionsSwitch release];
// Decrease Button
decreaseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // autoreleased
[decreaseButton setTitle:#"Decrease" forState:UIControlStateNormal];
[decreaseButton setTitleColor:oceanColor forState:UIControlStateNormal];
[decreaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
decreaseButton.frame = CGRectMake(20.0, 60.0, 110.0, 40.0);
[decreaseButton addTarget:self action:#selector(decrease) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:decreaseButton];
// Increase Button
increaseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // autoreleased
[increaseButton setTitle:#"Increase" forState:UIControlStateNormal];
[increaseButton setTitleColor:oceanColor forState:UIControlStateNormal];
[increaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
increaseButton.frame = CGRectMake(190.0, 60.0, 110.0, 40.0);
[increaseButton addTarget:self action:#selector(increase) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:increaseButton];
// Initialize the polygon
myPolygon = [[Polygon alloc] init];
numSidesValueLabel.text = [NSString stringWithFormat:#"%d", [myPolygon numberOfSides]];
// Prepare the canvas
CGRect myCanvasFrame = CGRectMake(20.0, 160.0, 280.0, 280.0);
myCanvas = [[Canvas alloc] initWithFrame:myCanvasFrame withNumSides:[myPolygon numberOfSides] withPolygonName:[myPolygon name]];
[self.view addSubview:myCanvas];
[myCanvas release];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
numSidesTextLabel = nil;
numSidesValueLabel = nil;
advancedOptionsLabel = nil;
myPolygon = nil;
myCanvas = nil;
[self.view release]; self.view = nil;
}
- (void)dealloc {
NSLog(#"(MainViewController): Dealloc is called");
[numSidesTextLabel removeFromSuperview];
[numSidesValueLabel removeFromSuperview];
[advancedOptionsLabel removeFromSuperview];
[myPolygon release];
[myCanvas removeFromSuperview]; // This should generate additional output
[super dealloc];
}
#end
Now let's have a look on the Canvas.h file:
#import <UIKit/UIKit.h>
#import "PrepDefs.h"
#interface Canvas : UIView {
IBOutlet UILabel * polygonNameLabel;
int currentStateNumSides;
CGPoint center;
}
#property (nonatomic, retain) UILabel * polygonNameLabel;
#property (nonatomic, assign) int currentStateNumSides;
#property (nonatomic, assign) CGPoint center;
+ (NSArray *) pointsForPolygonInRect:(CGRect) rect numberOfSides:(int) numberOfSides;
- (id) initWithFrame:(CGRect)frame withNumSides:(int)sides withPolygonName:(NSString *) name;
- (void) updateState:(int)sides withName:(NSString *)name;
#end
The Canvas.m file:
#import "Canvas.h"
#implementation Canvas
#synthesize polygonNameLabel, currentStateNumSides, center;
+ (NSArray *) pointsForPolygonInRect:(CGRect)rect numberOfSides:(int)numberOfSides {
CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0);
float radius = 0.9 * center.x;
NSMutableArray *result = [NSMutableArray array];
float angle = (2.0 * M_PI) / numberOfSides;
float exteriorAngle = M_PI - angle;
float rotationDelta = angle - (0.5 * exteriorAngle);
for (int currentAngle = 0; currentAngle < numberOfSides; currentAngle++) {
float newAngle = (angle * currentAngle) - rotationDelta;
float curX = cos(newAngle) * radius;
float curY = sin(newAngle) * radius;
[result addObject:[NSValue valueWithCGPoint:CGPointMake(center.x + curX, center.y + curY)]];
}
return result;
}
- (id) initWithFrame:(CGRect)frame withNumSides:(int)sides withPolygonName:(NSString *)name {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self setBackgroundColor:[UIColor brownColor]];
currentStateNumSides = sides;
center = CGPointMake ([self bounds].size.width / 2.0, [self bounds].size.height / 2.0);
polygonNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, center.y - 10.0, [self bounds].size.width, 20.0)];
polygonNameLabel.text = name;
polygonNameLabel.textAlignment = UITextAlignmentCenter;
polygonNameLabel.backgroundColor = [UIColor clearColor];
[self addSubview:polygonNameLabel];
[polygonNameLabel release];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
NSLog(#"(Canvas, -initWithFrame): You should always use initWithFrame:withNumSides:withPolygonName:");
return [self initWithFrame:frame withNumSides:DEFAULT_NUM_SIDES withPolygonName:DEFAULT_POLYGON_NAME];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
NSArray * points = [Canvas pointsForPolygonInRect:[self bounds] numberOfSides:[self currentStateNumSides]];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
[[UIColor blackColor] set];
UIRectFrame ([self bounds]);
CGContextBeginPath (currentContext);
for (int i = 0; i < [points count]; i++) {
CGPoint currentPoint;
NSValue * currentValue;
currentValue = [points objectAtIndex:i];
currentPoint = [currentValue CGPointValue];
//NSLog(#"(%.1f, %.1f)", currentPoint.x, currentPoint.y);
if (i == 0)
CGContextMoveToPoint(currentContext, currentPoint.x, currentPoint.y);
else
CGContextAddLineToPoint(currentContext, currentPoint.x, currentPoint.y);
}
CGContextClosePath(currentContext);
[[UIColor yellowColor] setFill];
[[UIColor blackColor] setStroke];
CGContextDrawPath(currentContext, kCGPathFillStroke);
}
- (void) updateState:(int)sides withName:(NSString *)name {
currentStateNumSides = sides;
polygonNameLabel.text = name;
}
- (void)dealloc {
NSLog(#"(Canvas): Calling dealloc");
[polygonNameLabel removeFromSuperview]; polygonNameLabel = nil;
[super dealloc];
}
#end
The PrepDefs.h file:
#ifndef __PREP_DEFS_H__
#define __PREP_DEFS_H__
#define MIN_NUM_SIDES_CONSTANT 3
#define DEFAULT_NUM_SIDES 5
#define MAX_NUM_SIDES_CONSTANT 12
#define DEFAULT_POLYGON_NAME #"Pentagon"
#endif
Finally the files for the Polygon class. The Polygon.h file:
#import <Foundation/Foundation.h>
#import "PrepDefs.h"
#interface Polygon : NSObject {
int numberOfSides;
int minimumNumberOfSides;
int maximumNumberOfSides;
}
#property int numberOfSides;
#property int minimumNumberOfSides;
#property int maximumNumberOfSides;
#property (readonly) float angleInDegrees;
#property (readonly) float angleInRadians;
#property (readonly) NSString * name;
- (void) setNumberOfSides:(int)numSides;
- (void) setMinimumNumberOfSides:(int)minNumSides;
- (void) setMaximumNumberOfSides:(int)maxNumSides;
- (id) initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min maximumNumberOfSides:(int)max;
#end
The Polygon.m file:
#import "Polygon.h"
#implementation Polygon
#synthesize numberOfSides, minimumNumberOfSides, maximumNumberOfSides, angleInDegrees, angleInRadians, name;
- (void) setNumberOfSides:(int)numSides {
if ((numSides >= [self minimumNumberOfSides]) && (numSides <= [self maximumNumberOfSides])) numberOfSides = numSides;
else NSLog(#"PolygonShape [setNumberOfSides]: Assignment out of bounds");
}
- (void) setMinimumNumberOfSides:(int)minNumSides {
if (minNumSides >= MIN_NUM_SIDES_CONSTANT) minimumNumberOfSides = minNumSides;
else NSLog(#"PolygonShape [setMinimumNumberOfSides]: Assignment out of bounds");
}
- (void) setMaximumNumberOfSides:(int)maxNumSides {
if (maxNumSides <= MAX_NUM_SIDES_CONSTANT) maximumNumberOfSides = maxNumSides;
else NSLog(#"PolygonShape [setMaximumNumberOfSides]: Assignment out of bounds");
}
- (float) angleInDegrees {
int sides = [self numberOfSides];
return ((float) (180.0 * ((double) (sides - 2)) / ((double) sides)));
}
- (float) angleInRadians {
int sides = [self numberOfSides];
return ((double) M_PI) * ((double) (sides - 2.0)) / ((double) sides);
}
- (NSString *) name {
NSString * s;
switch ([self numberOfSides]) {
case 3: s = [NSString stringWithString:#"Triangle"]; break;
case 4: s = [NSString stringWithString:#"Square"]; break;
case 5: s = [NSString stringWithString:#"Pentagon"]; break;
case 6: s = [NSString stringWithString:#"Hexagon"]; break;
case 7: s = [NSString stringWithString:#"Heptagon"]; break;
case 8: s = [NSString stringWithString:#"Octagon"]; break;
case 9: s = [NSString stringWithString:#"Enneagon"]; break;
case 10: s = [NSString stringWithString:#"Decagon"]; break;
case 11: s = [NSString stringWithString:#"Hendecagon"]; break;
case 12: s = [NSString stringWithString:#"Dodecagon"]; break;
default: NSLog(#"PolygonShape [name]: I should never enter here."); s = nil; break;
}
return s;
}
- (id) init {
return [self initWithNumberOfSides:DEFAULT_NUM_SIDES minimumNumberOfSides:MIN_NUM_SIDES_CONSTANT maximumNumberOfSides:MAX_NUM_SIDES_CONSTANT];
}
- (id) initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min maximumNumberOfSides:(int)max {
if (self = [super init]) {
// Cautiously initialize everything to zero
numberOfSides = 0;
minimumNumberOfSides = 0;
maximumNumberOfSides = 0;
// Attempt the actual assignment
[self setMaximumNumberOfSides:max];
[self setMinimumNumberOfSides:min];
[self setNumberOfSides:sides];
}
return self;
}
- (NSString *) description {
return [NSString stringWithFormat:#"Hello I am a %d-sided polygon (aka a %#) with angles of %.3f degrees (%.6f radians)", [self numberOfSides], [self name], [self angleInDegrees], [self angleInRadians]];
}
- (void) dealloc {
NSLog(#"(Polygon): Dealloc is called");
[super dealloc];
}
#end
After all the above, I believe the questions are:
Why dealloc functions are not used? Where do I actually have leaks?
When I initialize my mainController, should I pass as parameter the name of the nib file that was created when I created the custom class for the mainController?
Should I initialize the mainController and its view differently?
Should I use awakeFromNib instead of loadView or viewDidLoad?
Other comments that you have regarding the entire approach? Such as good or bad programming practices for one-screen applications? It really is a small program, so, I believe this is not an open-ended question. If however you think that this is an open-ended question, feel free not to answer. This is probably a naive question by a rookie to some veterans.
I really appreciate all the time that you spend on this and I am looking forward to your feedback.
I have 2 UIButtons setup(+/-) when tapped, the number changes by one within a UILabel. What do I need to do to have multiple UILabels and when one is touched I can change its current value with the UIButton(+/-)?
Have a variable that you use as the active label, then add a gesture recognizer to both the UIlabels to capture taps:
label.userInteractionEnabled = YES;
UIGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapRecieved:)];
[label addGestureRecognizer:tap];
[tap release];
//repeat for each additional label
Then in your tapReceived method, swap out the active label
-(void) tapRecieved:(UITapGestureRecognizer *)tap{
currentLabel = (UILabel *) tap.view;
}
Then in the method that you capture clicks to the +/- button, write to currentLabel
Edit: A quick and dirty implementation of your problem. In interface builder I made 2 labels and a button and hooked them up. When you tap a label, it becomes the currentLabel and when you tap the button, whichever label you chose is incremented by 1. Hope it helps.
.h
#import <UIKit/UIKit.h>
#interface junkViewController : UIViewController {
UILabel *label;
UILabel *label2;
UILabel *currentLabel;
int label1Value;
int label2Value;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#property (nonatomic, retain) IBOutlet UILabel *label2;
- (IBAction)buttonTap:(id)sender;
#end
.m
#import "junkViewController.h"
#implementation junkViewController
#synthesize label;
#synthesize label2;
- (void)dealloc
{
[label release];
[label2 release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
}
*/
-(void) tapRecieved:(UITapGestureRecognizer *)tap{
currentLabel = (UILabel *)tap.view;
NSLog(#"tap %#",tap.view);
}
-(void) viewDidLoad{
currentLabel = label;
label.text = [NSString stringWithFormat:#"%d",label1Value];
label2.text = [NSString stringWithFormat:#"%d",label2Value];
label.userInteractionEnabled = YES;
UIGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapRecieved:)];
[label addGestureRecognizer:tap];
[tap release];
tap = nil;
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapRecieved:)];
label2.userInteractionEnabled = YES;
[label2 addGestureRecognizer:tap];
[tap release];
}
- (void)viewDidUnload
{
[self setLabel:nil];
[self setLabel2:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)buttonTap:(id)sender {
int value = [currentLabel.text intValue] + 1;
currentLabel.text = [NSString stringWithFormat:#"%d",value];
}
#end
I had to do something similar recently for a dynamically created menu in my app. This worked well:
In the menu creation method:
#define NAV_WIDTH 220
#define NAV_HEIGHT 220
#define NAV_TOP_HEIGHT 16
#define NAV_ITEM_HEIGHT 30
#define NAV_BOTTOM_HEIGHT 24
#define NAV_LABEL_MARGIN 20
[navPage setFrame: CGRectMake(navLeftCorner, navTopCorner, NAV_WIDTH, NAV_HEIGHT)];
NSArray *menuTitles = [[NSArray alloc] initWithObjects: #"One", #"Two", #"Three", #"Four", nil];
int tag = 1;
double labelTop = NAV_TOP_HEIGHT;
for (NSString *title in menuTitles) {
UILabel *itemLabel = [[UILabel alloc] initWithFrame: CGRectMake(NAV_LABEL_MARGIN, labelTop, NAV_WIDTH - (NAV_LABEL_MARGIN * 2), NAV_ITEM_HEIGHT)];
[itemLabel setUserInteractionEnabled: YES];
UITapGestureRecognizer *menuTap = [[UITapGestureRecognizer alloc] initWithTarget: self action: #selector(menuTapped:)];
[itemLabel addGestureRecognizer: menuTap];
[itemLabel setText: title];
[itemLabel setTag: tag];
[navPage addSubview: itemLabel];
labelTop += NAV_ITEM_HEIGHT;
tag++;
}
. . . which calls this when tapped:
- (void) menuTapped: (UITapGestureRecognizer *)tap
{
UILabel *currentLabel = (UILabel *) tap.view;
int index = (currentLabel.tag - 1);
/* Do stuff based on the tag index */
}
NOTE: This is the ARC-compliant way; add [itemLabel release] after adding to the subview if you're not using ARC.
Is there something that I need to remember when using the windows-based template? Because I'm unclear as to why the tabs are showing up but nothing in the views are showing up.
Could you help? Because I've been searching through previous questions for a few hours now and I still haven't found anything to clear this up.
AnotherMadeUpAppDelegate.h
#import <UIKit/UIKit.h>
#import "AnotherMadeUpViewController.h"
#interface AnotherMadeUpAppDelegate : NSObject <UIApplicationDelegate> {
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#end
AnotherMadeUpAppDelegate.m
#import "AnotherMadeUpAppDelegate.h"
#implementation AnotherMadeUpAppDelegate
#synthesize window=_window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UIViewController *vc1 = [[UIViewController alloc] init];
UIViewController *vc2 = [[UIViewController alloc] init];
AnotherMadeUpViewController *vc3 = [[AnotherMadeUpViewController alloc] init];
UITabBarController *tbc = [[UITabBarController alloc] init];
tbc.viewControllers = [NSArray arrayWithObjects:vc1, vc2, vc3, nil];
[vc1 release];
[vc2 release];
[vc3 release];
[self.window addSubview:tbc.view];
[self.window makeKeyAndVisible];
return YES;
}
...
#end
AnotherMadeUpViewController.h
#import <UIKit/UIKit.h>
#interface AnotherMadeUpViewController : UIViewController<UIScrollViewDelegate>
{
IBOutlet UIPageControl *pageControl;
IBOutlet UIScrollView *scroller;
IBOutlet UILabel *label;
}
#property (nonatomic,retain)IBOutlet UIPageControl *pageControl;
#property (nonatomic,retain)IBOutlet UIScrollView *scroller;
#property (nonatomic,retain)IBOutlet UILabel *label;
-(IBAction)clickPageControl:(id)sender;
#end
AnotherMadeUpViewController.m
#import "AnotherMadeUpViewController.h"
#implementation AnotherMadeUpViewController
#synthesize pageControl,scroller,label;
-(IBAction)clickPageControl:(id)sender
{
int page=pageControl.currentPage;
CGRect frame=scroller.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[scroller scrollRectToVisible:frame animated:YES];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
int page = scrollView.contentOffset.x/scrollView.frame.size.width;
pageControl.currentPage=page;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
[label release];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
scroller.pagingEnabled=YES;
CGFloat labelOriginX = label.frame.origin.x;
CGFloat labelOriginY = label.frame.origin.y;
CGFloat scrollWidth = 0;
int pageNumber = 0;
for (int i=0; i<9; i++)
{
CGRect rect = label.frame;
rect.size.height = label.frame.size.height;
rect.size.width = label.frame.size.width;
rect.origin.x = labelOriginX + scrollWidth;
rect.origin.y = labelOriginY;
label.frame = rect;
label.text = [NSString stringWithFormat:#"%d", pageNumber];
label.textColor = [UIColor redColor];
[scroller addSubview:label];
pageNumber++;
scrollWidth += scroller.frame.size.width;
}
scroller.delegate=self;
scroller.directionalLockEnabled=YES;
scroller.showsHorizontalScrollIndicator=NO;
scroller.showsVerticalScrollIndicator=NO;
pageControl.numberOfPages=9;
pageControl.currentPage=0;
scroller.contentSize=CGSizeMake(pageControl.numberOfPages*self.view.frame.size.width, self.view.frame.size.height);
[self.view addSubview:scroller];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[label release];
self.label = nil;
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
Dissecting your viewDidLoad –
scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width,self.view.frame.size.height)];
You seem to be creating a new scroll view instance here but you have declared it as an outlet. If you don't have an outlet then remove the IBOutlet tag for scroller. If you do have one and want to use it then remove the above line. A shorter way of doing it would be,
scroller = [[UIScrollView alloc] initWithFrame:self.view.bounds];
Another thing is that you are creating 10 labels but assigning no frame. To show one of them each in different page,
int pageNumber = 0;
for (int i = 0; i < 10; i++)
{
UILabel *label = [[UILabel alloc] init];
[label sizeToFit];
label.center = CGPointMake (((2 * i + 1) * self.view.frame.size.width) / 2, self.view.frame.size.height / 2);
label.text = [NSString stringWithFormat:#"%d", pageNumber];
[scroller addSubview:label];
[label release];
pageNumber++;
}
and later set the contentSize to show 10 pages,
scroller.contentSize = CGSizeMake(10 * self.view.frame.size.width, self.view.frame.size.height);
The problem is with this line
AnotherMadeUpViewController *vc3 = [[AnotherMadeUpViewController alloc] init];
You need to change it to
AnotherMadeUpViewController *vc3 = [[AnotherMadeUpViewController alloc] initWithNibName:#"AnotherMadeUpViewController" bundle:nil];
Then your .xib will get loaded and your outlets will be connected.
And don't forget to connect your outlets to File's owner in IB.
Here is the idea:
The scroll view has several sub views, in this example, A, B, C:
[A][B][C]
Once the user scrolls to C, the view can't scroll anymore, but I want to loop back to A, like this:
[C][A][B]
When the user keeps scrolling to the right, the view keeps filling previous views in at the end. When the user
scrolls to left the view should also have similar behavior,
in order to make the user think that this view is infinitely long:
[A][B][C][A][B][C][A][B][C][A][B][C][A][B][C][A][B][C].....
How can I implement this in actual code? Thank you.
you need to set a delegate method that gets called when the view is scrolled, and then check any view that is more than one screens worth of pixels away from being visible, if it is then reposition the view 3*screen width to the other side.
This is what you want( on iphone 7 plus). This is pure code UI, no storyboard.First you need add sub code in your AppDelegate.m.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window makeKeyAndVisible];
ViewController *vc = [ViewController alloc];
self.window.rootViewController = vc;
self.window.backgroundColor = [UIColor redColor];
return YES;
}
Second add sub code in ViewController.m
#interface ViewController ()<UIScrollViewDelegate>
#property (nonatomic, strong) UIScrollView *readCannelScrollView;
#property (nonatomic, strong) UIImageView *pageOneView;
#property (nonatomic, strong) UIImageView *pageTwoView;
#property (nonatomic, strong) UIImageView *pageThreeView;
#end
#implementation ViewController
- (void)dealloc
{
_readCannelScrollView = nil;
_pageOneView = nil;
_pageTwoView = nil;
_pageThreeView = nil;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//容器的属性设置
self.readCannelScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.readCannelScrollView];
CGSize size = self.readCannelScrollView.contentSize;
size.width = 414*3;
self.readCannelScrollView.contentSize = size;
self.readCannelScrollView.pagingEnabled = YES;
self.readCannelScrollView.showsHorizontalScrollIndicator = NO;
self.readCannelScrollView.delegate = self;
self.readCannelScrollView.contentOffset = CGPointMake(0, 0);
//end
//添加页面1
self.pageOneView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 414, self.view.bounds.size.height)];
self.pageOneView.backgroundColor = [UIColor lightGrayColor];
self.pageOneView.image = [UIImage imageNamed:#"1"];
// self.pageOneView.font = [UIFont systemFontOfSize:80];
// self.pageOneView.textAlignment = NSTextAlignmentCenter;
[self.readCannelScrollView addSubview:self.pageOneView];
//添加页面2
self.pageTwoView = [[UIImageView alloc] initWithFrame:CGRectMake(828, 0, 414, self.view.bounds.size.height)];
self.pageTwoView.backgroundColor = [UIColor greenColor];
self.pageTwoView.image = [UIImage imageNamed:#"2"];
// self.pageTwoView.font = [UIFont systemFontOfSize:80];
// self.pageTwoView.textAlignment = NSTextAlignmentCenter;
[self.readCannelScrollView addSubview:self.pageTwoView];
//添加页面3
self.pageThreeView = [[UIImageView alloc] initWithFrame:CGRectMake(828, 0, 414, self.view.bounds.size.height)];
self.pageThreeView.backgroundColor = [UIColor grayColor];
self.pageThreeView.image = [UIImage imageNamed:#"3"];
// self.pageThreeView.font = [UIFont systemFontOfSize:80];
// self.pageThreeView.textAlignment = NSTextAlignmentCenter;
[self.readCannelScrollView addSubview:self.pageThreeView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark -
#pragma mark - scroll delegate
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSLog(#"scrollView.contentOffset.x=%f",scrollView.contentOffset.x);
CGFloat pageWidth = scrollView.frame.size.width;
int currentPage = floor((scrollView.contentOffset.x-pageWidth/2)/pageWidth)+1;
if (currentPage == 0)
{
UIImageView *tmpTxtView = self.pageThreeView;
self.pageThreeView = self.pageTwoView;
self.pageTwoView = self.pageOneView;
self.pageOneView = tmpTxtView;
}
if (currentPage == 2)
{
//换指针
UIImageView *tmpTxtView = self.pageOneView;
self.pageOneView = self.pageTwoView;
self.pageTwoView = self.pageThreeView;
self.pageThreeView = tmpTxtView;
}
//恢复原位
self.pageOneView.frame = (CGRect){0,0,self.pageOneView.frame.size};
self.pageTwoView.frame = (CGRect){414,0,self.pageTwoView.frame.size};
self.pageThreeView.frame = (CGRect){828,0,self.pageThreeView.frame.size};
self.readCannelScrollView.contentOffset = CGPointMake(414, 0);
}
#end
If you are satisfied with my answer give me a star.I need reputation.