2015年1月17日土曜日

Swift UICollectionViewをコードでつくる

UICollectionViewをコードでつくります

UICollectionViewDelegate, UICollectionViewDataSource UICollectionViewDelegateFlowLayout をUIViewController 内で実装します UIは, xibや, storyboardを使わずすべてコードで実装します セルやフッターなどのUIコードは省略します.
class ViewController : UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    private let barSize : CGFloat = 44.0
    private let kCellReuse : String = "PackCell"
    private let kCellheaderReuse : String = "PackHeader"
    private var collectionView : UICollectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout())   // Initialization
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        // Collection
        self.collectionView.delegate = self     // delegate  :  UICollectionViewDelegate
        self.collectionView.dataSource = self   // datasource  : UICollectionViewDataSource
        self.collectionView.backgroundColor = UIColor.clearColor()
 
        // Register parts(header and cell
        self.collectionView.registerClass(PackViewCell.self, forCellWithReuseIdentifier: kCellReuse) // UICollectionViewCell
        self.collectionView.registerClass(PackCollectionSectionView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: kCellheaderReuse)  // UICollectionReusableView
         
        self.view.addSubview(self.collectionView)
    }
 
    override func viewWillLayoutSubviews() {
        let frame = self.view.frame
        self.collectionView.frame = CGRectMake(frame.origin.x, frame.origin.y + barSize, frame.size.width, frame.size.height - barSize)
    }
 
    // MARK: UICollectionViewDelegate, UICollectionViewDataSource
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        var cell : PackViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(kCellReuse, forIndexPath: indexPath) as PackViewCell
        return cell    // Create UICollectionViewCell
    }
 
    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1  // Number of section
    }
 
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        var res = 0
        switch(section) {
        case 0:
            res = 4  // Number of cell per section(section 0)
            break
        default:
            res = 0
            break
        }
        return res
    }
 
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        collectionView.deselectItemAtIndexPath(indexPath, animated: false) 
        // Select operation
    }
 
    func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
        var reusableView : UICollectionReusableView? = nil
         
        // Create header
        if (kind == UICollectionElementKindSectionHeader) {
            // Create Header
            var headerView : PackCollectionSectionView = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: kCellheaderReuse, forIndexPath: indexPath) as PackCollectionSectionView
             
            reusableView = headerView
        }
        return reusableView!
    }
 
    // MARK: UICollectionViewDelegateFlowLayout
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        return CGSize(width: 90, height: 90) // The size of one cell
    }
 
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSizeMake(self.view.frame.width, 90)  // Header size
    }
 
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        let frame : CGRect = self.view.frame
        let margin  = (frame.width - 90 * 3) / 6.0
        return UIEdgeInsetsMake(10, margin, 10, margin) // margin between cells
    }
}
UICollectionViewDelegateFlowLayout の実装でセルのサイズや, ヘッダーのサイズ, セル間のマージンなどを決めています UICollectionViewDelegate, 実装していれば読み込まれるはずです コードそのものは, UITableViewの実装によく似ています. 行やセクションのナンバーから, セルを引っ張りだすことなど 違いは , レイアウトが柔軟なこと, サイズを返すところで行ごとにサイズを変更できます

2014年12月20日土曜日

WKWebView アラート(alert)を表示させる

省メモリや高機能が売りの, WKWebViewですが, いろいろ問題があるようです
alertの表示もその一つです
Webページ上で, JavaScriptでalert(); などのメソッドでダイアログを表示させようとすると, WKWebViewでは無反応になります
表示させる場合,タイプに応じて自前でコントロールを実装してあげる必要があります
カスタマイズができるので, 「いい」とも思われますが, 面倒ですよね
オリジナルはこちら(English), WkWebView Prompt 英語版の方には, 検索で見つけたテスト用のページのリンクを貼っておきました

WKUIDelegateを実装する

個人的な推奨ですが, これらのメソッドをUIViewControllerに実装して, WKWebViewを利用するUIViewControllerに
継承させれば, 楽かなと思っています

UIViewController.h
@interface ViewController : UIViewController

@end


UIViewController.m
@implementation ViewController

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)())completionHandler {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:webView.URL.host message:message preferredStyle:UIAlertControllerStyleAlert];
     
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        completionHandler();
    }]];
    [self presentViewController:alertController animated:YES completion:nil];
}
 
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
     
    // TODO We have to think message to confirm "YES"
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:webView.URL.host message:message preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        completionHandler(YES);
    }]];
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        completionHandler(NO);
    }]];
    [self presentViewController:alertController animated:YES completion:nil];
}
 
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
     
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:webView.URL.host preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
        textField.text = defaultText;
    }];
     
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
        completionHandler(input);
    }]];
     
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        completionHandler(nil);
    }]];
    [self presentViewController:alertController animated:YES completion:nil];
}


@end
この3つのメソッドの実装で, アラート, 選択式アラート, 入力ボックス付きアラートはカバーできます
ローカライズ言語部分は省略しています.
適宜置き換えてください

WKWebView スクリーンショット

UIWebViewで簡単に取得できたスクリーンショットも, WKWebViewでは同じ方法ではできません.

オリジナルはこちら (English),WKWebView Screenshots

UIViewのみを取得したい場合

snapshotViewAfterScreenUpdates を利用します。これだけで, UIViewまではO.K.です Objective-C
UIView *capturedView = [self snapshotViewAfterScreenUpdates:NO];
swift
var capturedView : UIView? = self.snapshotViewAfterScreenUpdates(false)
このUIViewは, 通常の方法でUIImageに変換できません。 drawViewHierarchyInRectを使うと, 常に NO, false が返ってきます UIViewを, Contextに描画できないようです シミュレータを使うと, drawViewHierarchyInRectでも描画できますが, 実機だと上記のようなことが起こります(iPhone5s, iPhone4s)

UIImageにしたい場合

上の方法だと実機で, UIView, UIImageのコンバートに失敗します ScrollViewの内容をそのまま描画してImageにしてしまえば, 実機でもうまくいきます
- (UIImage *)screenCapture {
    CGSize size = self.scrollView.contentSize;
    UIGraphicsBeginImageContextWithOptions(size, YES, 0);
    [self drawViewHierarchyInRect:CGRectMake(self.bounds.origin.x, self.bounds.origin.y, size.width, size.height) afterScreenUpdates:YES];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}
 
- (UIImage *)screenCapture:(CGSize)size {
    UIGraphicsBeginImageContext(size);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGFloat scale = size.width / self.layer.bounds.size.width;
    CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
    CGContextConcatCTM(ctx, transform);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

2014年11月22日土曜日

iOS WebView(UIWebView, WKWebView)をバージョンで分けて利用する

WebView

iOSでWebページを表示させる場合, UIWebViewを利用します. しかし, iOS8から, WKWebViewが登場しました.
WKWebViewは, Safariに搭載されている機能と同様の機能を含んでおり, Safariに似た,機能を利用できます.
またパフォーマンス面でも, WKWebViewは, UIWebViewを上回っています.
ですが, この機能は, WebKit というFrameworkを利用しており, これは, iOS8より利用できるので, iOS7以下ではりようできません そこで, OSのバージョンによりUIWebView, WKWebViewを分ければ, iOS8によりよい機能を提供できるのでは.

サンプル

UIWebView, WKWebViewをハンドルクラスでラップして, iOS7,8で使い分けられるようにする
UIWebViewの機能をベースにする
Delegateは, 外で実装, delegateのセットは内部で行う(WKWebViewは, Navigationのみ)
storyboardを使わずコードのみで実装


サンプルの構成



WebHandler : iOS7, iOS8で, UIWebView, WKWebViewを分ける, 機能もそれぞれ分ける
そのほかのView
カテゴリ : UIWebView+Info.m : UIWebViewで, タイトルや, URLを取得するための拡張機能 コードは, 一部省略しています(AppDelegate.mなど)

UIWebView+Info.h

#import 

@interface UIWebView (Info)

- (NSString *)title;
- (NSString *)URLString;
- (NSURL *)URL;

@end

UIWebView+Info.m

#import "UIWebView+Info.h"

@implementation UIWebView (Info)

- (NSString *)title {
    return [self stringByEvaluatingJavaScriptFromString:@"document.title"];
}

- (NSString *)URLString {
    return [self stringByEvaluatingJavaScriptFromString:@"document.URL"];
}

- (NSURL *)URL {
    return [NSURL URLWithString:self.URLString];
}

@end


WebHandler.h

#import 
#import 

typedef void (^CompletionBlock)(id, NSError*);

@interface WebHandler : NSObject

- (id)webView;
- (void)layout:(CGRect)frame;
- (void)setDelegate:(id)delegate;
- (void)clean;

// Load
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadURL:(NSString *)url;
- (void)stopLoading;

// Web history
- (void)goBack;
- (void)goForward;
- (BOOL)canGoBack;
- (BOOL)canGoForward;
- (BOOL)isLoading;

// JavaScript
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)javascript completion:(CompletionBlock)completion;

// Info
- (NSString *)title;
- (NSURL *)URL;

@end


WebHandler.m

#import "WebHandler.h"
#import 
#import "UIWebView+Info.h"

@interface WebHandler()

@property (nonatomic) UIWebView *uiwebView;
@property (nonatomic) WKWebView *wkwebView;
@property (nonatomic) BOOL ios8;

@end

@implementation WebHandler

- (id)init {
    if (self = [super init]) {
        self.ios8 = [WKWebView class] != nil;
        if (self.ios8) {
            self.wkwebView = [[WKWebView alloc] init];
        }
        else {
            self.uiwebView = [[UIWebView alloc] init];
        }
    }
    return self;
}

- (id)webView {
    if (self.ios8) {
        return self.wkwebView;
    }
    return self.uiwebView;
}

- (void)layout:(CGRect)frame {
    if (self.ios8) {
        self.wkwebView.frame = frame;

    }
    else {
        self.uiwebView.frame = frame;
    }
}

- (void)setDelegate:(id)delegate {  // This is for Navigation Delegate
    if (self.ios8) {
        self.wkwebView.navigationDelegate = delegate;
    }
    else {
        self.uiwebView.delegate = delegate;
    }
}

- (void)setUIDelegate:(id)delegate {  // This is for only ios8
    if (self.ios8) {
        self.wkwebView.UIDelegate = delegate;
    }
}

- (void)clean {
    if (self.ios8) {
        self.wkwebView.navigationDelegate = nil;
    }
    else {
        self.uiwebView.delegate = nil;
    }
    
    if ([self isLoading]) {
        [self stopLoading];
    }
}


#pragma mark - Common Features

/*
 * Load
 */
- (void)loadRequest:(NSURLRequest *)request {
    if (self.ios8) {
        [self.wkwebView loadRequest:request];
    }
    else {
        [self.uiwebView loadRequest:request];
    }
}

- (void)loadURL:(NSString *)url {
    [self loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}

- (void)stopLoading {
    if (self.ios8) {
        [self.wkwebView stopLoading];
    }
    else {
        [self.uiwebView stopLoading];
    }
}

/*
 *  History
 */
- (void)goBack {
    if (self.ios8) {
        [self.wkwebView goBack];
    }
    else {
        [self.uiwebView goBack];
    }
}

- (void)goForward {
    if (self.ios8) {
        [self.wkwebView goForward];
    }
    else {
        [self.uiwebView goForward];
    }
}

- (BOOL)canGoBack {
    if (self.ios8) {
        return [self.wkwebView canGoBack];
    }
    else {
        return [self.uiwebView canGoBack];
    }
}

- (BOOL)canGoForward {
    if (self.ios8) {
        return [self.wkwebView canGoForward];
    }
    else {
        return [self.uiwebView canGoForward];
    }
}

- (BOOL)isLoading {
    if (self.ios8) {
        return [self.wkwebView isLoading];
    }
    else {
        return [self.uiwebView isLoading];
    }
}

// goToBackForwardListItem is only for iOS8 WKWebView

#pragma mark - JavaScript
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)javascript completion:(CompletionBlock)completion {
    if (self.ios8) {
        [self.wkwebView evaluateJavaScript:javascript completionHandler:completion];
    }
    else {
        [self.uiwebView stringByEvaluatingJavaScriptFromString:javascript];
    }
}


#pragma mark - Info
/*
 * Info
 */
- (NSString *)title {
    if (self.ios8) {
        return [self.wkwebView title];
    }
    else {
        return [self.webView title];
    }
}

- (NSURL *)URL {
    if (self.ios8) {
        return [self.wkwebView URL];
    }
    else {
        return [self.webView URL];
    }
}
@end


UIHeaderView.h

#import 

@interface UIHeaderView : UIView

@property (nonatomic) UILabel *titleLabel;

@end


UIHeaderView.m

#import "UIHeaderView.h"

@implementation UIHeaderView

- (id)init {
    if(self = [super init]) {
        [self setBackgroundColor:[UIColor grayColor]];
        
        self.titleLabel = [[UILabel alloc] init];
        [self.titleLabel setTextColor:[UIColor whiteColor]];
        [self.titleLabel setTextAlignment:NSTextAlignmentCenter];
        [self addSubview:self.titleLabel];
    }
    return self;
}

- (void)layoutSubviews {
    self.titleLabel.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}

@end


UIFooterView.h

import 

@interface UIFooterView : UIView

@property (nonatomic) UIButton *backButton;
@property (nonatomic) UIButton *forwardButton;

@end


UIFooterView.m

#import "UIFooterView.h"

#define kBUTTONSIZE 80

@implementation UIFooterView

- (id)init {
    if(self = [super init]) {
        [self setBackgroundColor:[UIColor grayColor]];
        self.backButton = [[UIButton alloc] init];
        [self.backButton setTitle:@"Back" forState:UIControlStateNormal];
        [self.backButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        
        self.forwardButton = [[UIButton alloc] init];
        [self.forwardButton setTitle:@"Forward" forState:UIControlStateNormal];
        [self.forwardButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        
        [self addSubview:self.backButton];
        [self addSubview:self.forwardButton];
    }
    return self;
}

- (void)layoutSubviews {
    CGRect viewRect = self.frame;
    self.backButton.frame = CGRectMake(0, 0, kBUTTONSIZE, viewRect.size.height);
    self.forwardButton.frame = CGRectMake(0 + kBUTTONSIZE + 10, 0, kBUTTONSIZE, viewRect.size.height);
}
@end


ViewController.h

#import 
#import 

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"
#import "WebHandler.h"
#import "UIHeaderView.h"
#import "UIFooterView.h"

#define kHEADERHEIGHT 44
#define kFOOTERHEIGHT 44

@interface ViewController ()

@property (nonatomic) WebHandler *webhandler;
@property (nonatomic) UIHeaderView *header;
@property (nonatomic) UIFooterView *footer;
@property (nonatomic) int webCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    // Web
    self.webhandler = [[WebHandler alloc] init];
    [self.webhandler setDelegate:self];
    
    self.webCount = 0;
    
    // Header
    self.header = [[UIHeaderView alloc] init];
    
    // Footer
    self.footer = [[UIFooterView alloc] init];
    [self.footer.backButton addTarget:self action:@selector(backClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.footer.forwardButton addTarget:self action:@selector(forwardClick:) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:self.header];
    [self.view addSubview:self.webhandler.webView];
    [self.view addSubview:self.footer];
    
    [self.webhandler loadURL:@"http://google.com.sg"];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
   
    if (self.isViewLoaded && [self.view window] == nil) {
        [self dispose];
        self.view = nil;
    }
}

- (void)dealloc {
    [self dispose];
}

- (void)dispose {
    if (self.webhandler != nil) {
        [self.webhandler clean];
    }
}

- (void) viewWillLayoutSubviews {
    
    // Calculate layout
    CGRect viewRect = self.view.frame;
    self.header.frame = CGRectMake(viewRect.origin.x,
                                   viewRect.origin.y,
                                   viewRect.size.width,
                                   kHEADERHEIGHT);
    [self.webhandler layout:CGRectMake(viewRect.origin.x,
                                      viewRect.origin.y + kHEADERHEIGHT,
                                      viewRect.size.width,
                                      viewRect.size.height - kHEADERHEIGHT - kFOOTERHEIGHT)];
    
    self.footer.frame = CGRectMake(viewRect.origin.x,
                                   viewRect.origin.y + viewRect.size.height - kFOOTERHEIGHT,
                                   viewRect.size.width,
                                   kFOOTERHEIGHT);
    
}

#pragma mark - UIWebViewDelegate(iOS7)
- (void)webViewDidStartLoad:(UIWebView *)webView {
    // Start Loading
    NSLog(@"Start iOS7");
    self.webCount++;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    self.webCount--;
    if (self.webCount == 0) {
        NSLog(@"Finish iOS7");
        [self.header.titleLabel setText:[self.webhandler title]];
    }
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    NSLog(@"Erorr iOS7");
}

#pragma mark - WKWebViewDelegate(iOS8)
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"Start iOS8");
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    [self.header.titleLabel setText:[self.webhandler title]];
    NSLog(@"title %@", [self.webhandler title]);
    NSLog(@"Finish iOS8");
}

- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"Error iOS8");
}

#pragma mark - Button Click
- (void)backClick:(id)sender {
    if ([self.webhandler canGoBack]) {
        [self.webhandler goBack];
    }
}

- (void)forwardClick:(id)sender {
    if ([self.webhandler canGoForward]) {
        [self.webhandler goForward];
    }
}
@end


実行結果

iOS7, iOS8 ともに, 同じ結果が得られます


リファレンス

NS(WKWebView)
Professional Programmer

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];