Posted on

UIViewController切換、Notification、Delegate及繪圖練習

練習目標:http://www.youtube.com/watch?v=xiHBN2B1Vt4&list=UUPRP4bs_BNpx6XWI5Wm7O5g
老師範例:DrawSomething
我的作品:homework0810

在這個範例中,我使用了兩個viewController,去控制兩個頁面的畫面。

第一個用來顯示動畫的地方的viewController如下,
在這個頁面上除了球之外,還會有一個控制開關的鈕,當打開時代表不去管路徑如何直接讓球到圓點,
而關閉則會讓球一步步照著所繪的路線移動。
#import “ViewController.h”

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UISwitch *straightSwitch;
@property (weak, nonatomic) IBOutlet UIImageView *myBall;

@end

@implementation ViewController

– (void)viewDidLoad
{
[super viewDidLoad];
}

– (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}

/* 當切換畫面時會呼叫這個函數,可在此設定接收繪圖路徑資料的接收者為自己*/
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:@”toDrawing”]){
ViewController2 * modalView = (ViewController2 *) segue.destinationViewController;
modalView.delegate = self;
}
}

/*這個函數是讓另一個ViewController去呼叫的,傳入path*/
-(void) DrawViewController:(ViewController2 *) ViewController path:(NSMutableArray *) path{
if([path count] == 0) return;
if(self.straightSwitch.on){
NSValue *value = [path objectAtIndex:[path count]-1];
[self performSelector:@selector(changePosition:) withObject:value afterDelay:0];
}else{
for (int i=0; i<[path count]; i++) { NSValue *value = [path objectAtIndex:i]; [self performSelector:@selector(changePosition:) withObject:value afterDelay:i*0.1]; } } } /*改變球的位置*/ -(void)changePosition:(NSValue *) value{ CGPoint point = [value CGPointValue]; CGRect rect = [self.myBall frame]; rect.origin.x = point.x - 25; rect.origin.y = point.y - 25; [self.myBall setFrame:rect]; } @end[/code] 第二個view則是用來讓使用者繪製路線的地方, 在這邊我又將繪圖的地方獨立出來成為另一個繪圖版 而我讓view與繪圖間用notification來傳遞資料。 [code lang="c"]#import "ViewController2.h" #import "DrawPanel.h" @interface ViewController2 () @property (weak, nonatomic) IBOutlet UIImageView *myBall; @property (weak, nonatomic) IBOutlet DrawPanel * myDrawPanel; @end @implementation ViewController2{ NSMutableArray * path; } @synthesize myDrawPanel; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; //註冊監聽事件 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getPathData:) name:@"getPathData" object:nil]; } //監聽繪圖版的繪圖事件接收器 -(void) getPathData:(NSNotification *) note{ NSDictionary *dict = [note userInfo]; path = [dict valueForKey:@"path"]; } //將繪圖資料array傳回第一個view - (IBAction)ActionBtnTouch:(id)sender { [self dismissViewControllerAnimated:YES completion:^{ if([self.delegate respondsToSelector:@selector(DrawViewController:path:)]){ [self.delegate DrawViewController:self path:path]; } }]; } //在移除這個view時要順便將監聽事件移除 -(void)removeFromParentViewController{ [myDrawPanel removeObserver:self forKeyPath:@"price"]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end[/code] 這個則是繪圖版的code [code lang="c"]#import "DrawPanel.h" @implementation DrawPanel @synthesize path; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code } return self; } //每次碰觸時都清空array的值 -(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ path =[NSMutableArray array]; } //移動時將所移動到的位置紀錄下來 -(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint touchLocation = [touch locationInView:self]; NSValue *value = [NSValue valueWithCGPoint:touchLocation]; [path addObject:value]; [self setNeedsDisplay];//請view更新畫面 } //將資料傳給繪圖的view(發送notification) -(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ [[NSNotificationCenter defaultCenter] postNotificationName:@"getPathData" object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys: path , @"path",nil]]; } //實作繪圖方法 - (void)drawRect:(CGRect)rect{ CGContextRef context = UIGraphicsGetCurrentContext(); CGContextBeginPath(context); CGContextMoveToPoint(context, 55, 325); for(int i = 0;i<[path count];i++){ NSValue *value = [path objectAtIndex:i]; CGPoint newPoint = [value CGPointValue]; CGContextAddLineToPoint(context, newPoint.x, newPoint.y); } //CGContextClosePath(context); [[UIColor blackColor] setStroke]; CGContextDrawPath(context, kCGPathStroke); } @end[/code] 檔案下載:homework0810

Posted on

UIView及繪圖練習範例APP

作業目標:Youtube操作影片
練習原始碼:homework0803

這個作業主要是在練習對view以及簡單的繪圖的操作,還有timer的使用
因此我截錄一些我覺得是練習關鍵的程式碼

下面的是viewController的相關程式碼

一個個出現的程式碼,按下show按鈕時觸發

- (IBAction)showButtonClick {
    [self clearButtonClick];
    [self.circleNumber resignFirstResponder];
    total = [self.circleNumber.text integerValue];
    index = 0;
    //設定間隔一個個產生circle
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addItems:) userInfo:NULL repeats:YES];
}

取得圓圈排列坐標的程式碼,傳入值為圓半徑及角度

//取得坐標
- (CGPoint)getPoint:(int) radius withAngel:(CGFloat) degrees{

    int y = sin(degrees * M_PI / 180)*radius;
    int x = cos(degrees * M_PI / 180)*radius;
    CGPoint btCorner = CGPointMake(x, y) ;
    return btCorner;
}

下面的程式碼則是在被新增的UIView物件裡面

顯示動畫,這邊要注意的是因為定位點的關係,由於我們希望元件的縮放是以view的中心來做縮放
所以改變的值是self.bounds,一般我們在做元件內部繪圖事件,都會使用self.bounds
而self.frame則是在外部設定物件資訊時使用。

-(void) playAnimation{
    [UIView animateWithDuration:0.5f
                     animations:^{
                         self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, 50, 50);
                     }completion:^(BOOL finished) {
                         [UIView animateWithDuration:0.5f
                                          animations:^{
                                               self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, 30, 30);
                                          }completion:^(BOOL finished) {
                                          }];

                     }];
}

另外,因為- (id)initWithFrame:(CGRect)frame被呼叫的時間是在circle被新建的時後,而不是在被加到畫面時,
為了落實動畫的被執行時間,所以我又實作了layoutSubviews這個方法,
並加上BOOL init當今天是第一次被呼叫時,才會執行playAnimation

-(void)layoutSubviews{
    if(init){
        [self playAnimation];
        init = NO;
    }
}

然後實作按下後縮小離開時恢復大小的功能

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:0.2f
                     animations:^{
                         self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, 0, 0);
                     }
                     completion:^(BOOL finished) {
                     }];
}

-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{

    [UIView animateWithDuration:0.2f
                     animations:^{
                         self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, 30, 30);
                     }
                     completion:^(BOOL finished) {
                     }];

}

原始碼下載:homework0803

Posted on

簡易記帳APP範例

source code在此:範例檔案下載AppPrototype

難得周日在家,就把昨天老師說的練習做完
老實說其實我弄很久(大概有八~九小時跑不掉= =)
可見真的很不熟悉,debug之類都超級慢的

首先就是在跳去圖表的地方,
原本在實作實際功能前可以正常出現圖表,後來不知為何會當掉,
光這個無聊的bug就找了兩三小時(遮臉)

後來發現是因為下面這段程式碼

-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        InputViewController * inputCon = segue.destinationViewController;
        inputCon.delegate = self;
}

因為只要是storyboard控制的頁面切換,都會呼叫prepareForSegue
我同時用storyboard去切換新增資料和圖表頁面,
所以圖表的segue.destinationViewController不是InputViewController就會跳錯誤。

在這邊也要說,老師說的inputCon.delegate等於setDelegate是很重要的
因為錯誤訊息一直說沒有setDelegate這個方法
結果我還一直傻傻看不懂是什麼意思~>”<~
(老師上課有說!妳有沒有在聽阿!)

然後第二個遇到的困難,就是我要在轉圖表畫面時,需要把現有資料傳至圖表畫面
一開始我還傻傻的在那又把CostViewController弄一個@protocol
想說要實作一個delegate機制,又是一方面要實作別人的delegate,又要是別人的protocol,
搞得我好混亂阿~

後來才發現delegate似乎是被呼叫者,要把資料傳給呼叫者時才需使用(不確定,有錯請指正)

後來為了要傳遞資料,我不去使用storyboard的切換頁面方式,改使用程式去自行切換頁面
以下為傳送列表資料的程式碼

- (IBAction)showPieChart:(id)sender {
    PieChartViewController * pieController = [[PieChartViewController alloc] init];
    [self.navigationController pushViewController:pieController animated:YES];
    [pieController showPieChart:self record:self.record];
}

【附記一下切換viewController的相關方式】

最後要做的是實作修改和刪除資料的功能
第一步是先將CostViewController實作UITableViewDelegate

@interface CostViewController : UITableViewController<inputViewControllerDelegate,UITableViewDelegate>

本來我還很害怕實作完還要設什麼delegate,或還要自己去呼叫該方法,
結果太令人開心了,只要實作了tableView,按table cell時他就會自己呼叫(灑花)

-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    //修改該筆資料
    InputViewController *inputCon = [self.storyboard instantiateViewControllerWithIdentifier:@"inputCon"];
    [self.navigationController pushViewController:inputCon animated:YES];
    [inputCon modifyData:self.record[indexPath.row] forKey:indexPath.row];
    inputCon.delegate = self;
}

不過這樣子我還是遇到了一個問題,就是叫出來的畫面是一片黑。
百思不得其解也找不出原因,
後來嘗試的把原本在storyboard裡InputViewController和CostViewController的連接關係拿掉,
突然畫面就又跑出來了。

初步推論是或許一個畫面若用storyboard來切換就會無法自己寫程式碼去切換,
所以當有一個畫面有兩個按鈕都可以連接到時,就需要都自己用程式碼去做掉切換的動作。

剩的就把功能實作完就大功告成囉!

畫面介紹:一進入APP時的畫面
螢幕快照 2013-08-18 下午2.56.15
新增/修改記帳資料的畫面
螢幕快照 2013-08-18 下午2.56.32
帳目總計圖表
螢幕快照 2013-08-18 下午2.56.38

source code在此:範例檔案下載AppPrototype