2014年7月27日 星期日

iOS 開發筆記 - Admob SDK: Must set the rootViewController property of GADBannerView before calling loadRequest

又有一陣子沒把玩 Admob 了 XD 每次把玩的方式不同,這次看到錯誤訊息:
<Google> Must set the rootViewController property of GADBannerView before calling loadRequest:
以及:
didFailToReceiveAdWithError:Error
其他訊息:
<Google> To get test ads on this device, call: request.testDevices = @[ GAD_SIMULATOR_ID ];
現況與解法:

@interface YourViewController() <GADBannerViewDelegate>

@property (nonatomic, strong) GADBannerView *banner;

@end

@implementation YourViewController

- (GADBannerView *)banner {
    if (!_banner) {
        // 468*60
        _banner = [[GADBannerView alloc] initWithAdSize:kGADAdSizeFullBanner];
        _banner.adUnitID = @"ca-app-pub-###############";
    }
    return _banner;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.banner.frame = CGRectMake(0, self.view.frame.size.height > self.view.frame.size.width ? self.view.frame.size.width - 60 : self.view.frame.size.height - 60, 468, 60 );
    self.banner.delegate = self;
    [self.view addSubview:self.banner];

    // <Google> Must set the rootViewController property of GADBannerView before calling loadRequest:
    self.banner.rootViewController = self;

    GADRequest* req = [GADRequest request];
    // <Google> To get test ads on this device, call: request.testDevices = @[ GAD_SIMULATOR_ID ];
    req.testDevices = @[GAD_SIMULATOR_ID];
    [self.banner loadRequest:req];


    NSLog(@"self.banner: %@", self.banner);
}

- (void)adViewDidReceiveAd:(GADBannerView *)view
{
    NSLog(@"adViewDidReceiveAd");
}

- (void)adView:(GADBannerView *)view
didFailToReceiveAdWithError:(GADRequestError *)error
{
    NSLog(@"didFailToReceiveAdWithError:%@", error);
}

@end

iOS 開發筆記 - 使用 CocoaPods 與 Google Admob iOS SDK

繼續用 CocoaPods 練功,順手弄一下 Google Mobile Admob iOS SDK 6.10.0 版(changyy/GoogleMobileAdsSdkiOS.git),有需要可以從這邊使用:

$ vim Podfile
pod 'GoogleMobileAdsSdkiOS', :git => 'https://github.com/changyy/GoogleMobileAdsSdkiOS.git'


不過在 GoogleMobileAdsSdkiOS.podspec 這邊關於 authors, license 填寫反而是個問題 Orz 不知哪邊有好的介紹方式,暫時參考前輩們的用法。

此次撰寫 GoogleMobileAdsSdkiOS.podspec 時,花了不少時間在測試,主因是想要用 symbolic link 來管理版本更新的,例如建立 GoogleMobileAdsSdkiOS-latest 指向最新版版,藉以降低 podspec 的更動頻率:

project:
  • $ ln -s GoogleMobileAdsSdkiOS-6.10.0 GoogleMobileAdsSdkiOS-latest
podspec:
  • s.source_files = 'GoogleMobileAdsSdkiOS-latest/*.{h}'
  • s.preserve_paths = 'GoogleMobileAdsSdkiOS-latest'
  • s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => "$(PODS_ROOT)/GoogleMobileAdsSdkiOS/GoogleMobileAdsSdkiOS-latest/**" }
結果發現 s.preserve_paths 就只會 copy 那則 symbolic link 而已,也指不到東西 XD  雖然可以添加  GoogleMobileAdsSdkiOS-6.10.0 來解決,但 s.source_files 那邊似乎不支援 symbolic link 用法,直接去看 Pods/Header 沒法看到產生,這導致無法正常 import header 來使用,最後只好作罷,變成每次更新版本時,要更動與版本敘述相關的資料了( s.source_files、s.preserve_paths 跟 s.scconfig)

2014年7月25日 星期五

[SQL] 依據條件取出指定欄位 SELECT IF/ELSE, CASE WHEN/ELSE 用法 @ MySQL 5.6

假設有一張 Table 有兩個欄位:

mysql> SELECT * FROM mtable;
+----+----+
| f1 | f2 |
+----+----+
| 1  | 2  |
| 4  | 3  |
| 5  | 6  |
+----+----+


想要撈出 f1 跟 f2 之中,數值最大者:

mysql> SELECT f1 AS result FROM mtable WHERE f1 > f2;
+--------+
| result |
+--------+
| 4      |
+--------+

mysql> SELECT f2 AS result FROM mtable WHERE f1 < f2;
+--------+
| result |
+--------+
| 2      |
| 6      |
+--------+


這時候,可透過條件判斷,透過 CASE WHEN/ELSE 的用法,就可以不用分兩次撈了:

mysql> SELECT
CASE WHEN f1 > f2
THEN f1
ELSE f2
END AS result
FROM mtable;
+--------+
| result |
+--------+
| 2      |
| 4      |
| 6      |
+--------+

2014年7月23日 星期三

iOS 開發筆記 - 使用 GCDAsyncSocket / AsyncSocket 之處理 Partial Data 的用法

原本想寫寫 BSD Socket 的,再搭配 dispatch_async 其實也能運作的很好,但讓我想起去年底面試一間新創時,被問到有沒有用過 GCDAsyncSocket 的心得,看來用用已經常見的 framework / library 已是一種流行的或組織文化吧?至少接手的人可以有一樣的思維而不用再去認識另一個 coding style 吧!

把玩 GCDAsyncSocket 時,一開始試用了 readDataToData 函數,但發現這東西不是我想要的,也就是 server 沒有吐出預設字元(newline)時,程式端無法處理已接收的資料,但可以得知已經接收多少 Partial Data (可用在回報下載、上傳進度)。爾後用一些關鍵字(partial data)找找,發現也有人有這種需求:Is there a way to get partial data read and clear the buffer?

原來會有這種困惑只是不懂 GCDAsyncSocket 設計架構,搞懂後就一切安好啦。

There are 3 methods of reading data using GCDAsyncSocket / AsyncSocket:
  • readDataWithTimeout : reads chunks of data as they arrive
  • readDataToLength : read a specific length of data before returning result
  • readDataToData : read until finding a specific terminator

對於不能確定 Server 回傳的資料長度或特徵的行為,就適合採用 readDataWithTimeout 方式 :)

- (GCDAsyncSocket*)asyncSocket {
if(!_asyncSocket)
_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_queue_create("socket", NULL)];
return _asyncSocket;
}

- (NSMutableData *)buffer {
if (!_buffer) {
_buffer = [[NSMutableData alloc] init];
}
return _buffer;
}

- (void)viewDidLoad {
[super viewDidLoad];

if (![self.asyncSocket connectToHost:@"ServerIP" onPort:80 error:&error]) {
NSLog(@"ERROR: %@", error);
}
}

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
NSLog(@"didConnectToHost");
[sock readDataWithTimeout:-1 buffer:self.buffer bufferOffset:[self.buffer length] tag:1];
}

- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {
NSLog(@"didReadPartialDataOfLength");
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"didReadData(%lu): %@", tag, @([data length]));
[self.buffer setLength:0];
[sock readDataWithTimeout:-1 buffer:self.buffer bufferOffset:[self.buffer length] tag:tag];
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"socketDidDisconnect: %@", err);
}

- (void)socketDidSecure:(GCDAsyncSocket *)sock {
NSLog(@"socketDidSecure");
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"didWriteDataWithTag");
}

iOS 開發筆記 - 使用 UITapGestureRecognizer / UISwipeGestureRecognizer 偵測 UIView Click/Tap/Swipe Event (手指點擊、滑動事件)

沒把玩過,趁颱風天筆記一下 :P

One Click (Single Tap):

- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer;
tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapGestureRecognizer];
}

- (void)singleTap:(UIGestureRecognizer *)recognizer {
NSLog(@"singleTap: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Double Click (Double Tap):

- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer;
tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
tapGestureRecognizer.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:tapGestureRecognizer];
}

- (void)doubleTap:(UIGestureRecognizer *)recognizer {
NSLog(@"doubleTap: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Up:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeUp:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeUp:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeUp: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Down:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeDown:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeDown:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeDown: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Left:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeLeft:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeLeft:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeLeft: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Right:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRight:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeRight:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeRight: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


其他部分,大概是注意 UIView 跟 UIViewController 內定已經有把 events 接收處理的事項,例如 UIScrollView 就有捲動事件,假設 UIScrollView 只有提供左右捲動的功能,那就偵測不到 SwipeLeft 跟 SwipeRight ,但 SwipeUp 跟 SwipeDown 仍可以。而 Single Tap 跟 Double Tap 之間,基本上兩者存在時,點擊兩下的過程中,Single Tap Action 跟 Double Tap Action 都會被執行,並且 Single Tap Action 會先被執行。