2014年8月18日 星期一

iOS 開發教學 - 邀請使用者評論、評分 iOS app (Review/Rate your iOS app)

原理就是第一次使用時,埋一個時間進去,等下次使用者使用 iOS app 時,判斷時間是否夠長,達到時間間距時,使用 UIAleterView 詢問使用者是否願意 Rate your app。

假設 iOS app 預設啟用時,停留在某個 ViewController:

@interface ViewController () <UIAlertViewDelegate>
// ...
@end


@implementation ViewController

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    switch (alertView.tag) {
        case YOUR_APP_ID:
        {
            //NSLog(@"buttonIndex: %d", buttonIndex);
            NSDate * now = [[NSDate alloc] init];
            switch (buttonIndex) {
                case 1: // YES
                    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"itms-apps://itunes.apple.com/app/id%d", alertView.tag]]];
                    [[NSUserDefaults standardUserDefaults] setObject:now forKey:@"rateDone"];
                    break;
                case 2: // Remind me later
                    [[NSUserDefaults standardUserDefaults] setObject:now forKey:@"rateDate"];
                    break;
                default:
                    [[NSUserDefaults standardUserDefaults] setObject:now forKey:@"rateDone"];
                    break;
            }
            [[NSUserDefaults standardUserDefaults] synchronize];
        }
            break;
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    @try {
        //[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"rateDone"];
        //[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"rateDate"];
        //[[NSUserDefaults standardUserDefaults] synchronize];
     
        if (![[NSUserDefaults standardUserDefaults] objectForKey:@"rateDone"]) {
            NSDate * now = [[NSDate alloc] init];
            if (![[NSUserDefaults standardUserDefaults] objectForKey:@"rateDate"]) {
                [[NSUserDefaults standardUserDefaults] setObject:now forKey:@"rateDate"];
                [[NSUserDefaults standardUserDefaults] synchronize];
            } else {
                NSDate *prevDate = [[NSUserDefaults standardUserDefaults] objectForKey:@"rateDate"];
                if ([now timeIntervalSinceDate:prevDate] > 60 * 60 * 10) {
                    UIAlertView *alterView = [[UIAlertView alloc] initWithTitle:@"Rate this app" message:@"If you enjoy using this app, would you mind taking a moment to rate it?" delegate:self cancelButtonTitle:@"NO, Thanks" otherButtonTitles:@"YES", @"Remind me later", nil];
                    alterView.tag = YOUR_APP_ID;
                    [alterView show];
                }
            }
        }
    }
    @catch (NSException *exception) {
    }
    @finally {
    }
}

@end

2014年8月17日 星期日

iOS 開發教學 - UITableViewController 與 UIRefreshControl 用法 (Pull to Refresh)

呃,太久沒摸 UITableViewController 了,有一陣子都用 Storyboard 去刻,最近想說返樸歸真,改用純 coding 的方式,馬上想到如何處理 Pull to refresh 這個議題。

因為用 CocoaPods 一陣子,馬上就用關鍵字搜尋一下,發現有幾款不錯,但總是覺得差那麼一點,例如超過一年沒更新,當年熱門的甚至四年沒更新了 Orz 想了一會兒,才驚醒是不是變成內建了 XD 才想到之前去年底複習 CS193P 時,就看到教學過,那時是使用 Storyboard 的用法,隨意勾一勾就有的東西。

總之,回到主題,純寫 code 的方式,以 UITableViewController 而已,就是配置 self.refreshControl 就對了!

- (void)refreshData
{
    self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"Loading..."];
    dispatch_async(dispatch_queue_create("loading...", NULL), ^{
        [NSThread sleepForTimeInterval:5];
      
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.refreshControl endRefreshing];
            self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to refresh"];
        });
    });
}

- (void)viewWillAppear:(BOOL)animated
{
    if (!self.refreshControl)
        self.refreshControl = [[UIRefreshControl alloc] init];
    if (!self.refreshControl.isRefreshing) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height - self.navigationController.navigationBar.frame.size.height) animated:YES];
            [self.refreshControl beginRefreshing];
            [self refreshData];
        });
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    if (!self.refreshControl)
        self.refreshControl = [[UIRefreshControl alloc] init];
    if (self.refreshControl) {
        self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to refresh"];
        [self.refreshControl addTarget:self action:@selector(refreshData) forControlEvents:UIControlEventValueChanged];
    }
}


雖然很簡單,還是筆記一下 Orz

2014年8月14日 星期四

[PHP] 使用 Mantis SOAP API - 新增 issue custom_fields

架好 Mantis 後,預設是有開啟 SOAP API 的,可以瀏覽 http://server_ip/mantis/api/soap/mantisconnect.php?wsdl 試試,應該會看到 SOAP API 的使用列表跟使用方式。

對於 custom_fields 的用法,可用在新增一些額外的資料屬性來記錄,必須先新增 custom_fields:

Manage -> Manage Custom Fields -> 填寫想的欄位名字 -> New Custom Field -> 在 Write Access 請填寫 Reporter 以及 Display 的區域也勾一勾 -> update custom field,別忘了下方也要選擇使用的 project 並點選 Link Custom Field

接著,在使用 Mantis SOAP API 時,必須先用 mc_project_get_custom_fields 把 custom_fields 的資料撈出來,因為在使用 mc_issue_add 時,對於 custom_fields 必須填寫 id 跟 name:

// 使用 Hash 來記錄
$custom_fields = array();
foreach( $c->mc_project_get_custom_fields($user, $pass, $project_id) as $raw )
        if (isset($raw->field->name) )
                $custom_fields[$raw->field->name] = $raw->field;


最後再新增 issue data 時,記得 custom_fields 是一個 Array of Object,所以新增的額外欄位資料,請用:

$field_item = new stdClass();
$field_item->field = $custom_fields["Custom_field_name"];
$field_item->value = $custom_fields_value["Custom_field_name"];


例如:

$issue_data = array(
        'project' => array(
                        'id' => $project_id
                ),
        'category' => $issus_category,
        'summary' => $issue_summary,
        'description' => $issue_description,
'custom_fields' => array()
);

$field_item = new stdClass();
$field_item->field = $custom_fields["Custom_field_name"];
$field_item->value = $custom_fields_value["Custom_field_name"];

array_push( $issue_data['custom_fields'] , $field_item );

$issus_id = $c->mc_issue_add($user, $pass, $issue_data);

2014年8月12日 星期二

[PHP] 使用 Mantis SOAP API - Automatically Create an Issue



架好 Mantis 後,預設是有開啟 SOAP API 的,可以瀏覽 /mantis/api/soap/mantisconnect.php?wsdl 試試,應該會看到 SOAP API 的使用列表跟使用方式。

使用上需要指定 user, password 跟 project id,目前沒找到一口氣列出所有 project 的方法?倒是可以用 project name 找 project id,而發一則 issue 必須要有的資訊:project id, category, summary, description,連續動作:

<?php
$c = new SoapClient('http://YourMantisServerIP/mantis/api/soap/mantisconnect.php?wsdl');

$user = 'report_account';
$pass = 'report_password';
$project_name = 'ProjectName';

$issue_summary = 'Report title';
$issue_description = 'Report body';
$issus_category = 'iOS';

$project_id = $c->mc_project_get_id_from_name($user, $pass, $project_name);
if ($project_id == 0) {
        echo "[ERROR] Project ID Not Found: [$project_name]\n";
        exit;
}

$categories = $c->mc_project_get_categories($user, $pass, $project_id);
print_r($categories);

//$custom_fields = $c->mc_project_get_custom_fields($user, $pass, $project_id);
//print_r($custom_fields);

$issue_data = array(
        'project' => array(
                        'id' => $project_id
                ),
        'category' => $issus_category,
        'summary' => $issue_summary,
        'description' => $issue_description
);

$issus_id = $c->mc_issue_add($user, $pass, $issue_data);

echo "Issue ID: $issus_id\n";


成功的話,就會看到最後印出的 Issue ID。

對於 SOAP 的簡介可以參考:如何透過PHP、SOAP 及 WSDL撰寫Web Service

2014年8月11日 星期一

AWS 筆記 - 透過 Script 半自動化處理 MySQL Replication Error

用了 AWS RDS 一陣子了,之前一直沒搞懂為何 call mysql.rds_skip_repl_error; 一次只能清一條 Error ,直到另一台 MySQL Slave Server 出錯,變成也要手動清除錯誤訊息時,才搞懂原理就是這樣,一次只能清一條。

所以,對於 MySQL Replication Error 不知不覺就生了兩種 script 來應付,一種是 RDS 當 MySQL Replication Slave 用的,另一個則是一般 MySQL Replication Slave Server 用的:

處理 AWS RDS MySQL Replication Slave Servers:

#!/bin/sh
a=0
while [ $a -lt 1000 ]
do
        check=`echo "SHOW SLAVE STATUS \G" | mysql -h YOUR_AWS_RDS_SERVER -u root -ppassword | grep "Last_SQL_Error:" | grep -c "Could not execute"`
        if [ $check -eq 0 ] ; then
                break
        fi
        echo "CALL mysql.rds_skip_repl_error;" | mysql -h YOUR_AWS_RDS_SERVER -u root -ppassword
        a=$(( $a + 1 ))
done


處理 MySQL Replication Slave Servers:

#!/bin/sh

a=0
while [ $a -lt 2000 ]
do
        check=`echo "SHOW SLAVE STATUS \G" | mysql -h YOUR_MYSQL_SLAVE_SERVER -u root -ppassword | grep "Last_SQL_Error:" | grep -c "Error "`
        if [ $check -eq 0 ] ; then
                break
        fi
        check=`echo "STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; START SLAVE;" | mysql -h localhost -u root -ppassword`
        a=$(( $a + 1 ))
        sleep 3
done


以上是用在 MySQL Server 彼此算異質系統,只能先透過這招頂替了。