Posts match “ ios ” tag:

Replacing NSLog with trace

ActionScript 寫習慣了,trace 一下改不掉..XD

include/Trace.h
#ifdef IPHONESIM

#define trace(c) NSLog(@"%s [Line %d] %s", __PRETTY_FUNCTION__, __LINE__, c)
#else
#define trace(...)

#endif

編成 extension,記得 include

include/Extension.h
#ifndef EXTENSION_H
#define EXTENSION_H

#include "Trace.h"

namespace extension {
...
}
Makefile
all: clean build

build:
        haxelib run hxcpp Build.xml -Diphoneos -DHXCPP_ARMV7
        haxelib run hxcpp Build.xml -Diphonesim

debug: clean
        haxelib run hxcpp Build.xml -Diphoneos -DHXCPP_ARMV7
        haxelib run hxcpp Build.xml -Diphonesim -D DHXCPP_STACK_TRACE

clean:
        rm -rf obj

rebuild it.
make

要用的時候

trace("hello");

記得開 simulator
openfl test ios -simulator -debug -verbose

Output.log
... Demo[1993:a0f] void -[DemoDelegate foo](DemoDelegate*, objc_selector*) [Line 70] hello

Finding out the UDID

iOS 7 之後,有使用 UDID 的 app 將無法上架,
掃一下看有沒有用到 uniqueIdentifier

grep -rnis 'uniqueIdentifier' "App.app/App"

for all architectures:

strings - -a -arch all "App.app/App" | grep 'uniqueIdentifier'

for armv7:

strings - -a -arch armv7 "App.app/App" | grep 'uniqueIdentifier'

Checking iOS version

Define macros

#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

Usage:
iOS < 7.0

if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
  ...
}

iOS >= 6.1.3

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.1.3")) {
  ...
}

HTTP GET/POST request on iOS

GET

跟 as 用法一樣,基本上沒什麼問題。

POST

記得要指定 request.method = URLRequestMethod.POST
不然會音訊全無..

Debug

開啟 verbose ,真的很好用。
request.verbose = true;

#if cpp
var variables:URLVariables = new URLVariables();
variables.Name = "isobar";
variables.Parameter = Json.stringify(obj); // json

var request:URLRequest = new URLRequest(Config.BASE_URL + __api);
request.verbose = true;
request.method = URLRequestMethod.POST;
request.data = variables;

var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, function(e:Event) {
    trace("e:"+e.target.data.toString());
});
loader.load(request);
#end

Convert Image to Base64

iOS

Convert JPEG to Base64

UIImageJPEGRepresentation/UIImagePNGRepresentation 將 UIImage 轉成 NSData,
再丟給 Base64 encode 處理。

// initialize
[Base64 initialize];
// resize: 150x150
NSData *imageData = UIImageJPEGRepresentation([self resizeImage:[info objectForKey:UIImagePickerControllerEditedImage] Size:CGSizeMake(150, 150)], 1.0);
NSString *strEncoded = [Base64 encode:imageData];

Haxe

Convert bytesData to BitmapData

Base64 decode 取得 BytesData 後,直接丟給 BitmapData.loadFromHaxeBytes 處理。

var bmd:BitmapData = BitmapData.loadFromHaxeBytes(Base64.decodeBytesData(data));
var bitmap:Bitmap = new Bitmap(bmd);
addChild(bitmap)

iOS

Convert String to Base64

丟給 sendEvent 時記得轉成 C string(char)。

[Base64 initialize];
NSString *str = @"hello world~大家好!!";
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSString *strEncoded = [Base64 encode:data];

// pass c string: const char
sendEvent(2, [strEncoded cStringUsingEncoding:NSASCIIStringEncoding]);

Haxe

Output String
trace(Base64.decodeBytesData(data).toString());

Update 20131104

iOS7 之後內建 NSData 終於支援 Base64 encoding/decoding。

encode:
[[sourceString dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
decode:
NSData *decodeData = [[NSData alloc] initWithBase64EncodedString:encodeString options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSString *output = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];

Passing Data Between View

ViewA

view 切換前會呼叫 prepareForSegue,
segue 物件同時包含開始及結束兩個 view controllers,
記得幫 Storyboard Segue Identifier 命名 gotoViewB

ViewAController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([[segue identifier] isEqualToString:@"gotoViewB"]) {
    id vc = segue.destinationViewController;
    [vc setValue:@"hello world 大家好" forKey:@"dataString"];
  }
}

ViewB

在 viewDidLoad 即可取到值。

ViewBController.h
@property (strong) NSString *dataString;
ViewBController.m
- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"output:%@", self.dataString);
  //output:hello world 大家好
}

Default Launch Image for iOS

project.xml
<launchImage path="Assets/Default.png" width="320" height="480" />
<launchImage path="Assets/Default@2x.png" width="640" height="960" />
<launchImage path="Assets/Default-568h@2x.png" width="640" height="1136" />
<launchImage path="Assets/Default-Portrait~ipad.png" width="768" height="1024" />
<launchImage path="Assets/Default-Portrait@2x~ipad.png" width="1536" height="2048" />
<launchImage path="Assets/Default-Landscape~ipad.png" width="1024" height="768" />
<launchImage path="Assets/Default-Landscape@2x~ipad.png" width="2048" height="1536" />

QR Code in iOS7

iOS7 之後內建 QR code/Bar code 功能,
不用再安裝 ZBar 等第三方 library,方便許多,
使用流程如下:

  • configure device
  • create session(協助處理input/output)
  • add input
  • add output
configure device
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]){
    if([device lockForConfiguration:&error]) {
        CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
        [device setFocusPointOfInterest:autofocusPoint];
        [device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
        [device unlockForConfiguration];
    }
    else{
        NSLog(@"configuration error");
    }
}
create session
AVCaptureSession *session = [[AVCaptureSession alloc]init];
add input
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if ([session canAddInput:input]) {
    [session addInput:input];
}
else {
    NSLog(@"can not add input");
}
add output

setMetadataObjectTypes 必需在 addOutput 之後,不然會噴 error[1]。

AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
if([session canAddOutput:output]){
    [session addOutput:output];
    [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    // setMetadataObjectTypes must be call after addOutput
    [output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
}

大致上這樣就完成了,但真正執行時,空白一片,
加個 preview layer 把 camera video 顯示出來,這樣感覺好多了...:D

AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
captureVideoPreviewLayer.frame = self.view.frame;
captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:captureVideoPreviewLayer];

[session startRunning];

20150731 更新:
回來看兩年前的 code 居然還是花了不少時間,決定包成 BarcodeManager 方便以後使用,詳見 github

[1]*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVCaptureMetadataOutput setMetadataObjectTypes:] - unsupported type found. Use -availableMetadataObjectTypes.'

Reference

How to create a basic UITableView

File -> New -> Project -> Single View Application
Open storyboard -> Drag "Table View" to View Controller
Press crtl and drag line from "Table View" to ViewController.h

ViewController.h
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property NSMutableArray *arrDataSource;

@end

NSArray: static array
Cannot be changed after the array has been initialized.

NSMutableArray: dynmaic array
Can be modified after they have been created.
Here we used NSMutableArray.

Added two protocols:
UIViewController should be added UITableViewDelegate/UITableViewDataSource protocols.

ViewController.m
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  _arrDataSource = [[NSMutableArray alloc] init];
  for (int i=0; i<100; i++) {
    [_arrDataSource addObject:[NSString stringWithFormat:@"index:%d", i]];
  }

  self.tableView.delegate = self;
  self.tableView.dataSource = self;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return [_arrDataSource count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  static NSString *cellIdentifier = @"MyReuseCellIdentifier";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  if(!cell){
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  }

  cell.textLabel.text = [_arrDataSource objectAtIndex:indexPath.row];
  return cell;
}

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

@end

We just declared the arrDataSource by using "@property" in ViewController.h and no longer need to "@synthesize" in ViewController.m file.
@synthesize arrDataSource = _arrDataSource;
The compiler will auto generate getter/setter, now we can use _arrDataSource.

// Code generated in background, doesn't actually appear in your application
- (NSMutableArray *)arrDataSource {
  return _arrDataSource;
}

- (void) setArrDataSource:(NSMutableArray *)arrDataSource {
  _arrDataSource = arrDataSource;
}

-(void)viewDidLoad

  • initial arrDataSource
  • set delegate = self
  • set dataSource = self (not to arrDataSource)

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

  • number of row in the section

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

  • create reusable cells or a custom cell using tags
  • set cell data

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

  • Called after the user changes the selection

enjoy it~~

Reference

Creating An Array

NSMutableArray *arrDataSource = [[NSMutableArray alloc] init];
for (int i=0; i<100; i++) {
  [arrDataSource addObject:[NSString stringWithFormat:@"HelloWorld-:%d", i]];
}

Xcode Keyboard Shortcuts

comment: cmd + /
code indent: ctrl + i
shift right: cmd + ]
shift left: cmd + [
move word left(right): alt + left or alt + right
move to begin(end) of line: alt + up or alt + down
edit all in the scope: cmd + ctrl + e

go to declaration: cmd + double click
switch .h and .m: cmd + ctrl + up

open quickly: cmd + shift + o

show detail: option + click

goto line number: cmd + l
open preferences: cmd + ,
Show: Line numbers

show/hide debug area: cmd + shift + y
show/hide left utilities: cmd + option + 0

SDWebImage for iOS

SDWebImage 預設是暴力快取。

只要載入過的圖檔都會被強制快取在本地端,
除非把程式移除,不然不會更新快取。
優點是效能非常好,就算離線狀態也能顯示。
缺點是當圖檔更新的時候,不會跟著更新。

[uiimageview setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://lorempixel.com/400/200/cats/%d", 1+indexPath.row%9]] placeholderImage:[UIImage imageNamed:@"previewholder"]];
SDWebImageRefreshCached: 依照 HTTP Caching Control Header 來更新圖檔。
[uiimageview setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://lorempixel.com/400/200/cats/%d", 1+indexPath.row%9]] placeholderImage:[UIImage imageNamed:@"previewholder"] options:SDWebImageRefreshCached];
To clean both memory and disk cache.
SDImageCache *imageCache = [SDImageCache sharedImageCache];
[imageCache clearMemory];
[imageCache clearDisk];
[imageCache cleanDisk];

Fixing iOS7 status bar issue in the storyboard

The status bar issue for both iOS6 and iOS7

iPhone 3.5-inch iOS 6 - Offset by 20 pixels
iPhone 3.5-inch iOS 7 - Can't see bottom of screen
iPhone 4-inch iOS 6 - Offset by 20 pixels

Using AutoLayout to fix it

Step1 - Adjust the height of the table view

Step2 - Ctrl + drag Top Layout Guide to table view

Step3 - Selected Vertical Spacing

Step4 - Verical Spacing Constraint

Step5 - Same as above, ctrl + drag Bottom Layout Guide to table view

It's working....enjoy it~~

iOS Push Notifications

可以先參考這篇 蘋果消息推送服務教程:第三部分 講得很清楚。

註冊推播通知
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
檢查使用者推播設定
// 檢查使用者推播設定
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types & UIRemoteNotificationTypeAlert) {
  NSLog(@"Notification Enabled");
  // 可以把token傳到server,之後server就可以靠它送推播給使用者了
  [[HVPApiManager sharedManager] apiPushServiceWithToken:strDevToken withEnable:YES];
}
else {
  NSLog(@"Notification not enabled");
  [[HVPApiManager sharedManager] apiPushServiceWithToken:strDevToken withEnable:NO];
}
輸出註冊錯誤原因
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
  NSLog(@"Failed to register for remote notifications:%@", error);
}

常見的錯誤有:

Error Domain=NSCocoaErrorDomain Code=3000 "no valid 'aps-environment' entitlement string found for application"

可以先確認 Provisioning Profile 是否正確,device 是否已經註冊,另外 push notifications 只能在實機上測試。

手動開啟關閉推播
// enable
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
// disable
[[UIApplication sharedApplication] unregisterForRemoteNotifications];
接收通知

送出來的訊息格式

{
    "aps": 
    {        
      "alert": "message(推播訊息)",        
      "badge": 1,        
      "sound": "default"    
    },    
    "type": "1",
    "title": "title",
    "id": "234"
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
  for (id key in userInfo)
  {
    NSLog(@"KEY=%@, VALUE=%@", key, [userInfo objectForKey:key]);
  }
}

參考1
參考2
參考3

iOS Push Notification Redirect to View when App becomes Active

點選推播通知,開啟 app 並跳到特定的 view。
首先參考這篇,起碼要先接得到通知訊息。

第一種狀況,當 app 沒開啟也沒在背景執行,接收到推播的時候會觸發didFinishLaunchingWithOptions
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // register for remote notification
  [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
  
  // app 如果在沒啟動的狀態下(前景/背景都無),點"推播通知"後,會將推播資料以 launchOptions 傳入。
  NSDictionary *remoteNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
  if (remoteNotification) {
    [self application:application didReceiveRemoteNotification:remoteNotification];
  }

  return YES;
}
第二種狀況,當 app 在背景執行時,接收到推播的時候會觸發didReceiveRemoteNotification
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
  // This function called when receive notification and app is in the foreground.
  for (id key in userInfo) {
    trace(@"Key=[%@], Value=[%@]", key, [userInfo objectForKey:key]);
  }
  
  NSString *openType = [userInfo objectForKey:@"open"];
  NSString *link = [userInfo objectForKey:@"link"];
  [[HVPApiManager sharedManager] saveNotificationWithOpenType:openType withLink:link];
  
  /* Output Badge number */
  trace(@"Badge: %@", [[userInfo objectForKey:@"aps"] objectForKey:@"badge"]);
}

Getting the Custom UITableViewCell from its superview in iOS7

I am getting the custom UITableViewCell on button click.
It worked fine in iOS5/6 but crashed in iOS7.

- (void)handleClick:(id) sender {
  // use superview
  UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
  NSIndexPath *indexPath = [self.tableView indexPathForCell:clickedCell];
  NSLog(@"indexPath:%d, textLabel:%@", indexPath.row, clickedCell.textLabel.text);
}

I got this error in iOS7.

[UITableViewCellScrollView btn]: unrecognized selector sent to instance

For iOS7: Apple has changed the view hierarchy.

UITableViewCell *clickedCell = (UITableViewCell *)[[[sender superview] superview] superview];

It's better to fix this with position.

  // use position
  CGPoint btnPosition = [sender convertPoint:CGPointZero toView:self.tableView];
  NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:btnPosition];
  UITableViewCell *clickedCell = (UITableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];

Update[2014/12/11]

iOS8 又改回來,所以比較好的方式是用 position 來取得 indexPath。

Convert Constants to NS_ENUM in iOS

Constants
file.h
extern const int COACHMARK_ALL;
extern const int COACHMARK_NEW;
@interface ...
...
@end
file.m
const int COACHMARK_ALL = 8;
const int COACHMARK_NEW = 3;
@implementation ...
...
@end
NS_ENUM
file.h
typedef NS_ENUM(NSInteger, CoachMark) {
  CoachMarkAll = 8,
  CoachMarkNew = 3,
  CoachMarkMagicNumber = 1,
};
@interface ...
...
@end

More info on NS_ENUM is available at NSHipster.