2014年10月18日土曜日

iOS8 ステータスバー対策

どうやらiOS8 SDKから, landscape時にステータスバーなくなりました.  WWDCでのデモどおり, そして, beta, GMとそのままでXCode6.0.1と最終的にステータスバーはやっぱりなくなるっぽいです.
SDKを8に変更してコンパイルすると, iphoneのlandscape時にステータスバーが自動で消えます, ipadはそのまま残ります XCode5のままだとステータスバーは残ります
landscape時に,  より画面のスペースを確保するのが狙いらしいですが, アプリの作りによっては大問題です.


アプリの作り方における問題

UIを作る方法は,
  • storyboard
  • xib
  • コード
この場合, 問題になるのは, コードですべて開発している場合です. storyboardや, xibの場合はよしなにやってくれるはずです.

対策

ステータスバーの高さを動的に計算します. UIViewControllerのviewWillLayoutSubviews, UIViewのlayoutSubviewsメソッド内で, レイアウト(frame)の計算をおこないます. これらのメソッドは回転時にも呼ばれるので, そこで修正が可能です. ステータスバーの計算は以下で行います
 
CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
CGFloat statusBarHeight = statusBarSize.height

サンプル

headerとその下にborderを入れたものです. もちろんstoryboardは使用していません.
 
#import "ViewController.h"

#define kHEADERHEIGHT 44

@interface ViewController ()

@property (nonatomic) UIView *headerView;
@property (nonatomic) UIView *borderView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view setBackgroundColor:[UIColor whiteColor]];
    
    // Add Header
    self.headerView = [[UIView alloc] init];
    [self.headerView setBackgroundColor:[UIColor cyanColor]];
    
    self.borderView = [[UIView alloc] init];
    [self.borderView setBackgroundColor:[UIColor grayColor]];
    
    [self.view addSubview:self.headerView];
    [self.view addSubview:self.borderView];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)viewWillLayoutSubviews {
    CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
    
    self.headerView.frame = CGRectMake(0, statusBarHeight, self.view.frame.size.width, kHEADERHEIGHT);
    self.borderView.frame = CGRectMake(0, statusBarHeight + kHEADERHEIGHT, self.view.frame.size.width, 0.5);
}
@end

Swift

import UIKit

let kHEADERHEIGHT : CGFloat = 44.0

class ViewController: UIViewController {
    
    var headerView : UIView
    var borderView : UIView
    
    override init() {
        self.headerView = UIView()
        self.borderView = UIView()
        super.init()
    }
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        self.headerView = UIView()
        self.borderView = UIView()
        super.init(nibName: nibName, bundle: nibBundle)
    }

    required init(coder aDecoder: NSCoder) {
        self.headerView = UIView()
        self.borderView = UIView()
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = UIColor.whiteColor()
       
        self.headerView.backgroundColor = UIColor.cyanColor()
        
        self.borderView.backgroundColor = UIColor.grayColor()
        
        self.view.addSubview(self.headerView)
        self.view.addSubview(self.borderView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewWillLayoutSubviews() {
        
        let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.height
        self.headerView.frame = CGRectMake(0, statusBarHeight, self.view.frame.size.width, kHEADERHEIGHT)
        self.borderView.frame = CGRectMake(0, statusBarHeight + kHEADERHEIGHT, self.view.frame.size.width, 0.5)
    }
}

結果

iOS Simulator iOS8.0 iPhone4sでの結果です

portrait時です

landscape時です
ステータスバーがなくなりました.

これをiOS7.1のsimulatorでやると, ステータスバーは残ったままになります

2014年8月9日土曜日

UIIVewに穴をあける

ひさびさの投稿になりました 最近は, iOSとサーバーサイドの仕事にほぼ集中するような状態です 今回はUIViewに穴をつくり, 下のViewを表示するというViewをつくります Original English version is here.

早速ですが例です

結果から先に表示すると,
このような形になります. "Touch me!"というのは, UIButtonで, UIViewControllerの上に乗っています その上に, 青い薄い色のUIViewを重ねています. 通常ですと, この薄い色がボタンに重なるはずですが, ホールを作っているので下がくっきり見えています.

このUIの実現方法

上に重ねるViewに対して, UIRectFillを使って描画します. 色は, 透明を使います.
HoleView.h
#import 
@interface HoleInViewController : UIViewController
@end
HoleView.m
#import "HoleView.h"
 
@implementation HoleView
 
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}
 
- (void)drawRect:(CGRect)rect
{
    // Drawing code
    CGRect hole = CGRectMake(100, 100, 100, 40);
    [[UIColor clearColor] setFill];
    UIRectFill(hole);
}

このViewの使い方

ヘッダーは省略します
#import "HoleInViewController.h"
#import "HoleView.h"
 
@interface HoleInViewController ()
 
@property (nonatomic)UIButton *button;
@property (nonatomic)HoleView *holeView;
 
@end
 
@implementation HoleInViewController
 
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
     
    self.button = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.button setTitle:@"Touch me!" forState:UIControlStateNormal];
    [self.button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
     
    self.view.backgroundColor = [UIColor whiteColor];
     
    self.holeView = [[HoleView alloc] init];
    self.holeView.backgroundColor = [UIColor colorWithRed:0.0 green:0.1 blue:1.0 alpha:0.3];
     
    [self.view addSubview:self.button];
    [self.view addSubview:self.holeView];
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
-(void)viewWillLayoutSubviews {
     
    self.button.frame = CGRectMake(100, 100, 100, 40);
    self.holeView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
@end
これで先ほど見せた例は完成です. さて次回は円のホールの作り方です

2014年5月17日土曜日

Magical Record DAO

前回に続いて, Magical Recordに関する話題です.
前回は, セットアップの方法と, 利用するための初期化のコードまで書きました
今回は実際の操作(DAO)をつくります
こちらが前回の分
Magical Record 最初のステップ

データ構造

今回使用するデータはこんな感じです
Country
  • code Integer32
  • key String
Countryという名前のNSManagedObjectで, Integerと, Stringの2つのデータを持つ場合です
uniqueなキーの用なものが必要であれば, もう少し工夫が必要です
さて次はこれらのCRUD処理を書いていきます

Create 作成

-(Country *)createCountry:(int)code key:(NSString *)key {
    Country *country = [Country MR_createEntity];
    country.code = [NSNumber numberWithInt:code];
    country.key = key;
    return country;
}
利用方法
CountryDAO *cdao = [[CountryDAO alloc] init];
[cdao createCountry:1 key:@"goodtimebiz.us"];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
最後にcommitにあたるコードをいれます

Read データをとってくる

Find All と, 属性での検索の例をのせます
-(NSArray *)findAll {
    return [Country MR_findAll];
}

-(Country *)findByCode:(int)code {
    return [Country MR_findFirstByAttribute:@"code" withValue:[NSNumber numberWithInt:code]];
}
codeで検索します

Delete

データが多い場合は, 一気に消すために truncateを使いましょう
1件ずつ消す場合は, MR_deleteAllMatchingPredicateを使います
-(void)deleteAll {
    [Country MR_truncateAll];
}

Update

Updateは特に解説するまでもないです
findなどでデータをとってきて, データを直接更新最後に
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
を忘れずに

NSPredicate

少し, 条件を複雑にしたい場合, SQLのWHERE部分に相当する部分は, CoreData同様, Magical RecordでもNSPredicateを利用します
少し例をかえます. Itemという新たなデータを作りました Item
  • from Date
  • name String
  • to Date
  • uid String
少し複雑になりました
Deleteの例
-(void)deleteByUid:(NSString *)uid {
     
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uid == %@", uid];
    [Item MR_deleteAllMatchingPredicate:predicate];
}

NSPredicateを使って, uidという値で検索して, deleteします.検索にかかったものをdeleteします
さらに複雑な例ですfromという時間を利用します
-(NSArray *)findBetweenFromTo:(NSDate *)from to:(NSDate *)to {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(from >= %@) AND (to <= %@)", from, to];
    return [Item MR_findAllSortedBy:@"from" ascending:YES withPredicate:predicate];
}

ユニークなキー

CoreDataは自動的にprimary key的なものが追加されますが, 開発者側ではアクセスできません
自分自身で primary key 的なものを管理したい場合新たに, ユニークなキーを要素として追加する必要があります
個人的にはこんな感じに作っています
Item *item = [Item MR_createEntity];
NSString *uid = [[[item objectID] URIRepresentation] lastPathComponent];

2014年5月11日日曜日

Magical Record 最初のステップ

iOSでSQLiteを使用していればおそらくCore Dataを使っているかと思います。
Core Dataの登場で, DBの操作はずいぶん楽になりました. ORMapperがあると楽ですよね.
Core Dataを普通に使う場合, コネクションの部分や, Contextの処理は自身で書きます
これが意外に面倒だったりします.
Core Dataを普通に利用する場合はこちらでどうぞ.(English)

Core Data


Magical Record

このCoreDataをさらにwrapするライブラリ, Magical Recordがあります.
Rubyの, Active Recordの操作感で実装できるwrapperです.
上で書いたような処理を内部でやってくれるのでデータ処理の部分のみに集中できるという利点があります
今回は導入部分のみで, 次回データ操作(DAO)の部分を扱います
DAOはこちら

ステップ

導入におけるステップです
  • CoreData.frameworkをリンク
  • Magical Recordのソースをgithubから持ってきて, プロジェクトにそのまま入れる
  • xcdatamodeldファイルを準備(ここは普通のCoreDataと同じでよい)
  • ModelファイルをXCodeから作成
  • AppDelegateにコードを書く

1, 3はCoreDataと同じです. Core Data関連は筆者の別ブログ(English)上で確認してください

AppDelegate

AppDelegateにコードを書き足します. ピュアCoreDataと比べるとずいぶんシンプルです
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    [MagicalRecord setupCoreDataStackWithStoreNamed:@"Model"];
     
    return YES;
} 

一行です. Modelのところはファイル名です. 適宜かえてください. さてこれで使用する準備は整いました. 次回は実際の操作です

Core Data テーブルの追加

iOSでCore Data利用時に, テーブル(スキーマ)を追加する方法です。
サーバーサイドでは, スキーマの変更などはよくあります. テーブルの追加,カラムの追加削除, などなどです.
スキーマの変更コードをさらっと書いておしまい. というわけですが... iOSでももちろんそういったケースは考えられます.
Core Dataではどのようにするのでしょうか? English

今回取り扱うケース

  • すでにリリース済み xcdatamodelがすでに存在する
  • 新しいデータを追加(テーブルの追加にあたる)

How to?

新しいバージョンの .xcdatamodelを追加

まず, xcdatamodelはバージョンで管理していることを押さえましょう.
XCodeでは, rootのxcdatamodelが表示されています. 追加すると, モデル一覧が出てきます

さて, バージョンを追加しましょう
rootのxcdatamodelをポイントします
次に, "New File" -> "Core Data" -> "Data Model" と選択します
新しい xcdatamodelを追加できます. ここで, 名前を決めて終了です.

名前ですが, モデルの番号かもしくはソフトウェアのバージョンをつけておくとわかりやすいです


新しいバージョンにスイッチ

次に先ほど作成したバージョンにファイルを切り替える操作をします.
xcdatamodelは選択できます. つまり戻したりできます.
もういちど, rootのxcdatamodelをポイントします. File Inspector を観ましょう.
下の方に, "Model Version" というのがあります.
ここのドロップダウンで選択できます

新しいバージョンのファイルにデータを追加

先ほどの新しいファイルにデータを追加します
CustomHolidayというのを追加しました
基本的に, スキーマ変更のためのコードは,自動的にやってくれます. カラムの追加時, データ構造の変更時であれば, overrideして操作コードを書かないといけませんが

2014年4月7日月曜日

UIDatePickerをしまっておくUIをつくった

こんにちは, 今回は, UIDatePickerに関する話題です。
UIDatePickerは, 日時を選択するためのUIで配置するだけでりようできるので便利ですが, サイズが大きいので場所をとりますよね.
なので, 見た目しまっておけるようなUIを作ってみました

TextDatePickerという名前でGithubで公開しています.
UITextFieldのようなところにボタンをつけました. このボタンを押すと, UIDatePickerが登場します.
もう一度タップすると, UIDatePickerをしまうことができます
UIPickerで変更した結果はその段階で文字列の時間に反映されます

使い方はGithubに書いてある通りです
ソースおよびリソースをコピーして, 数行書くだけで利用できます

このままでは使いづらいところもあるでしょうから, コードなどをいじってみてもよいかもしれません
UIViewを重ねたViewになっています.
一部iOS7のアニメーションを使っています




2014年3月29日土曜日

Java(Android)時間操作ライブラリ

iOS に続いて, Java(Android)用に, 時間まわりのラリブラリを書きました GitHubで公開しています JavaDatekit build.xmlを準備しておきましたので, ant をお持ちの方は,
ant
で, dst/datekit.jar が作成されますので PureJavaでコーディングしていますので, Androidでも動きます

概要

時間操作に関わる操作を, Builderでまとめてみました. 基本的な操作はCalendarクラスを使って, Date, Calendarの相互の変換などを入れました Builderになっている部分で操作を行い, getのメソッドでデータを操作もしくは取得, format系のメソッドでString形式のデータを取得します

使い方

ソースコードをまるごとコピーする もしくは, antを使って, datekit.jar を作成してそれをライブラリとしてプロジェクトにコピーします(BuildPathを通します)

メソッド

コンストラクタ
  • public DateBuilder()
  • 初期化です現在時刻で初期化します
  • public DateBuilder( TimeZone zone )
  • TimeZoneを使って初期化します
  • public DateBuilder( Date date )
  • Dateを使って初期化します
builder系
  • public DateBuilder on( Date date )
  • Dateインスタンスをセットします
  • public DateBuilder addDay( int day )
  • day分だけ日にちを足します
  • public DateBuilder subDay( int day )
  • day分だけ日にちを引きます
  • public DateBuilder begin()
  • 日にちの最初00:00:00を取得します
  • public DateBuilder end()
  • 日にちの最後23:59:59を取得します
  • public DateBuilder firstDateOfMonth()
  •  現在月の最初の日を取得します
  • public DateBuilder lastDayOfMonth()
  •  現在月の最後の日を取得します
  • public DateBuilder nextMonthFirst()
  •  次の月の最初の日を取得します
  • public DateBuilder getFromYMD( int year, int month, int day )
  • 年, 月, 日をセットします
  • public DateBuilder getDayofDay( int day )
  • 曜日をセットします 0 日曜日, 6 土曜日
  • public DateBuilder getGMT()
  • GMTのタイムゾーンをセットします
get系
  • public int getYear()
  • 年を取得します
  • public int getMonth()
  •  月を取得します
  • public int getHour()
  • 時間を取得します(24h)
  • public int getSecond()
  • 秒を取得します
  • public Date getTime()
  • Dateインスタンスを取得します
  • public Calendar getCal()
  • Calendarインスタンスを取得します
  • public boolean isWeekDay()
  • WeekDay(ビジネスデイ)の判定をします
  • public boolean isWeekEnd()
  •  週末かどうかを判定します
  • public boolean before ( Calendar calendar )
  • 引数calendarとBuilderに保持している日時を比較して, 保持しているものが前ならtrueを返します
  • public boolean before ( Date date )
  • 引数dateとBuilderに保持している日時を比較して, 保持しているものが前ならtrueを返します
  • public boolean after ( Calendar calendar )
  • 引数calendarとBuilderに保持している日時を比較して, 保持しているものが後ならtrueを返します
  • public boolean after ( Date date )
  • 引数dateとBuilderに保持している日時を比較して, 保持しているものが後ならtrueを返します
  • public DateBuilder copy()
  • DateBuilderをコピーして新しいDateBuilderを作成します
format系 Builderで持っている情報をStringで返します
  • public String formatYear()
  • yyyy形式で返します
  • public String formatMonth()
  • MM形式で返します
  • public String formatDay()
  • dd形式で返します
  • public String formatA()
  • yyyy-MM-dd形式で返します
  • public String formatB()
  • yyyy/MM/dd形式で返します
  • public String formatFull()
  • yyyy/MM/dd HH:mm:ss形式で返します

サンプル

テストより 年, 月, 日をセットします
DateBuilder builder = new DateBuilder();
builder.getFromYMD(2014, 3, 11);  
System.out.println(builder.formatA());  // 2014/03/11
2014/03/11の00:00:00をCalendarインスタンスで取得します
Calendar cal = builder.getFromYMD(2014, 3, 11).begin().getCal();
GMT
DateBuilder builder = new DateBuilder(TimeZone.getTimeZone("GMT"));