SwiftのextensionでUITextFieldにInset余白を追加(メモ)


UITextFieldにInset 10ピクセルの余白を追加する。
Objective-CではCategoryで鷹揚にやっていたことが、お手軽に書ける感じになった。

extension UITextField {
    func textRectForBounds(bounds: CGRect) -> CGRect {
        return CGRectInset(bounds, 10, 10)
    }
    func editingRectForBounds(bounds: CGRect) -> CGRect {
        return CGRectInset(bounds, 10, 10)
    }
}

iOSの軽いアプリならクラスのファイルの冒頭にでも書いてもいいかも。

Swiftで半角ASCII文字判定


前にもObjective-Cで同じ課題について書いたが、かなり昔の話しで正規表現を使うやり方だった。今回は NSCharacterSet を使う。

今回やりたいのは文字列の1文字目が半角文字か日本語漢字なのかの判定。

結論的には以下。titleStrの1文字目が半角文字かどうかを unicodeScalarsを使い、valueで判定。

let hankaku = NSCharacterSet(range: NSMakeRange(0x20, 0x7e))
let titleUniStr = titleStr.unicodeScalars
let initialChar = titleUniStr[titleUniStr.startIndex]
if hankaku.longCharacterIsMember(initialChar.value) {
    println("半角")
} else {
    println("全角")
}

なお、iOSには文字列の判定に使える以下のようなセットが予め用意されているが、これらは全角判定に使えない。なぜなら、漢字も下記の letters だし、全角の数字も下記の digits に含まれるからだ。

let letters = NSCharacterSet.letterCharacterSet()
let digits = NSCharacterSet.decimalDigitCharacterSet()
let alphanum = NSCharacterSet.alphanumericCharacterSet()

以上

SwiftでQuickDialogを使ってFontPickerを作る


QPickerElementを使って、フォントの種類とサイズを選べるFontPickerを作る。

このとき PickerView にはフォントの種類に応じてフォント名をそのフォントの書体で表示するようにしたい。
これをやるにはQPickeElementが pickerView のハンドルでやっている
– (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
ではなく、UILabelを使って
– (UIView*)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
で表示しなければならないのでObjective-Cのカテゴリを使ってQPickerElementを拡張する(これは後述)。

とりあえず、FontPicker用のQPickerElementの準備。
QPickerElement は今のところ Extras の中に入っているので、cocoapod にQuickDialog/Extrasも追加。
Podsの Profile

pod 'QuickDialog'
pod 'QuickDialog/Extras'

SWiftで使うために myAppxxxxxxx-Bridging-Header.h に以下を追加。
後に述べるQPickerElementの拡張のためのヘッダも併記。

#import <QuickDialog/QuickDialog.h>
#import <QuickDialog/QPickerElement.h>
#import "QPickerTableViewCellExtension.h"

QPickerElementさえ使えれば、FontPickerを作るのはそう難しくはない。

var fontNames: [String] = []
var fontSizes: [String] = []
for family in UIFont.familyNames() as [String] {
    for fontname in UIFont.fontNamesForFamilyName(family) as [String] {
        fontNames.append(fontname)
    }
}
fontNames.sort{  $0 < $1 }
for fontsize in  Array<Int>(8...50) as [Int] {
    fontSizes.append(String(fontsize))
}
let fontPickerSection = QSection()
let fontPickerElement = QPickerElement(title: "Font Picker", items: [fontNames, fontSizes], value: fontValue)
fontPickerElement.key = "fontPicker12345"
fontPickerSection.addElement(fontPickerElement)
root.addSection(fontPickerSection)

値の取得は、rootからほかのElementなどとまとめてとってもよい。

let dict = NSMutableDictionary()
root.fetchValueIntoObject(dict)

しかし、onValueChanged で、その都度取って NSUserDefaults などに保存する方が好み。

fontPickerElement.onValueChanged = { ( el: QRootElement!) in
    var dict = NSMutableDictionary()
    fontPickerElement.fetchValueIntoObject(dict)
    .... dict を NSUserDefaultなどに格納 ....
}

最後に、PickerViewでフォント名の表示をそのフォントで表示する拡張のコード。これはSwiftではなく Objective-Cで書く。
拡張するのは QPickerElementではなく QPickerTableViewCell の方である。
QPickerTableViewCellExtension.h を追加する。

#import <Foundation/Foundation.h>
#import "QPickerTableViewCell.h"
#import "QPickerElement.h"

@interface QPickerTableViewCell(QPickerTableViewCellExtension)

@property (nonatomic, strong) UIPickerView *pickerView;
@property (nonatomic, readonly) QPickerElement *pickerElement;

- (UIView*)pickerView;

@end

QPickerTableViewCellExtension.m を追加する。

#import "QPickerTableViewCellExtension.h"

@implementation QPickerTableViewCell(QPickerTableViewCellExtension)

@dynamic pickerView;
@dynamic pickerElement;

// add following for font picker by eiji
- (UIView*)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
{
    UILabel* fontlabel = (UILabel*)view;
    if (!fontlabel) {
        fontlabel =  [[UILabel alloc] initWithFrame:CGRectZero];
        fontlabel.adjustsFontSizeToFitWidth = YES;
        fontlabel.backgroundColor = [UIColor clearColor];
    }
    [fontlabel setFrame:CGRectMake(0.0f, 0.0f, ([pickerView rowSizeForComponent:component].width - 8.0f),
                                   [pickerView rowSizeForComponent:component].height)];
    if (component == 0) {
        NSString* fontname = [[[self.pickerElement.items objectAtIndex:(NSUInteger) component] objectAtIndex:(NSUInteger) row] description];
        fontlabel.text = fontname;
        fontlabel.font = [UIFont fontWithName:fontname size:[UIFont labelFontSize]];
        fontlabel.textAlignment = NSTextAlignmentLeft;
    } else {
        NSString* fontsize = [[[self.pickerElement.items objectAtIndex:(NSUInteger) component] objectAtIndex:(NSUInteger) row] description];
        fontlabel.text = fontsize;
        fontlabel.font = [UIFont boldSystemFontOfSize:15];
        fontlabel.textAlignment = NSTextAlignmentRight;
    }
    return fontlabel;
    
}

これでフォント名の書体で表示されるPickerができる。

SwiftでQuickDialogをpushViewControllerで使う(メモ)


QuickDialgはDailogと名付けられているのでモーダルでしか使えないのか?
UINavigationControllerでpushViewControllerしたい。

また、この時、なんとかQuickDialgViewControllerをサブクラス化して、その中でQRootElementを作れないかも試行錯誤してみたが、こっちはちょっと無理っぽい。

単純にQuickDialgのサンプルを参考に以下のようにした。
1.QRootElementはクラス・メソッドで別途作る。
2.上記をrootとして渡してQuickDialogControllerのインスタンスを作り、これをpushViewControllerする。

1.QRootElementはクラス・メソッドで別途作る

QuickDialgのサンプルにならって何たらBuilderのcreateメソッドという名前にする。
Swiftではクラス・メソッドは class func create() と書けばよいらしい。
ExampleQRootBuilder.swift

class ExampleQRootBuilder: NSObject {
    class func create() -> QRootElement {
        let root = QRootElement()
        root.title = "Hello"
        root.grouped = true
        let section = QSection(title: "Example")
        let label = QLabelElement(title: "Hello", value: "Hello!")
        root.addSection(section)
        section.addElement(label)
        return root
    }
}

2.QuickDialogControllerとpushViewController

let root = ExampleQRootBuilder.create()
let qdc = QuickDialogController(root: root)
self.navigationController.pushViewController(qdc, animated: true)

QuickDialogControllerはサブクラスにして戻るボタンなどカスタマイズしてもよい。

以上

SwiftでStoryBoardとJASidePanelsとCoreDataを使う(メモ)


SwiftでStoryBoardを使って、JASidePanels(Objective-C)とCoreDataを使うメモ。
JASidePanelsはcocoapods経由で組み込んだ。cocoapodsの説明は割愛。
JASidePanelsのcenterPanelはUINavigationContorollerにして、そこでCoreDataを使うようにコーディングする。

StoryBoardでSceneを作成

以下のようにした。
1.JASidePanelsの土台になるUIViewControllerをSBに追加 → ラベル: MainPanel
2.leftPanelになるUITableViewContorollerをSBに追加 → ラベル: SidePanelLeft
3.rightPanelになるUITableViewContorollerをSBに追加 → ラベル: SidePanelRight
4.centerPanelになるUINavigationContorollerをSBに追加 → ラベル: Master
5.そのNavigationContorollerのtopViewControllerになるUITableViewControllerをSBに追加
6.個々のデータの編集用にUIViewContorollerをSBに追加
fig01

 

各Sceneに「Class」と「Storyboard ID」を設定

fig021.MainPanel Scene
  Class: MainPanelViewController
2.SidePanelLeft Scene
  Class: (UITableViewContoroller)
  Storyboard ID: leftPanel
3.SidePanelRight Scene
  Class: (UITableViewContoroller)
  Storyboard ID: rightPanel
4.Master Scene
  Class: (UINavicationController)
  Storyboard ID: centerPanel
5.Master Scene – topViewController
  Class: DataListViewController
  ※topViewController設定→SB上でcontrolキーを押しながらMaster SceneからこのSceneへドラッグ

Is Initial View Controller設定
MainPanel SceneはInitial View Controllerとなるので、Attribute Inspectorパネルの「Is Initial View Controller 」にチェックを入れる。

Bridging-Header.h

Objective-C で書かれた JASidePanels を使うには xxxxx-Bridging-Header.h が必要
ここでは MyApplicaton-Bridging-Header.h に下記コードを追加

#import <JASidePanels/JASidePanelController.h>
#import <JASidePanels/UIViewController+JASidePanel.h>

AppDelegateの記述

AppDelegateのdidFinishLaunchingWithOptions、本来は何も書かなくてもJASidePanelsは表示されるが、今回はCoreDataを使うので、centerPanelであるUINavigationControllerのtopViewControllerにself.managedObjectContext を渡すための処理を書く。

    func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
        let mainPanel = self.window!.rootViewController as MainPanelViewController
        let navigationController = mainPanel.centerPanel as UINavigationController
        let controller = navigationController.topViewController as DataListViewController
        controller.managedObjectContext = self.managedObjectContext
        return true
    }

MainPanelViewController.swift

1.MainPanelViewControllerはJASidePanelControllerのサブクラス。
2.awakeFromNibの中身は swift風には下記でよいようだ。

class MainPanelViewController: JASidePanelController {
    override func awakeFromNib() {
        self.leftPanel = self.storyboard?.instantiateViewControllerWithIdentifier("leftPanel") as UIViewController
        self.rightPanel = self.storyboard?.instantiateViewControllerWithIdentifier("rightPanel") as UIViewController
        self.centerPanel = self.storyboard?.instantiateViewControllerWithIdentifier("centerPanel") as UIViewController
    }

SwiftでもStoryBoard経由でObjective-c版のJASidePanelsをCoreDataとともに使えることが分かった。メモはここまで。

CoreData SQLiteファイルにmatadataを追加する


CoreDataでSQLiteのファイルのmetadataに独自のキーを追加したい場合。
Core Data Programming Guide: Using Persistent Stores の最後の例を参考に、SQLite用にしてみた。storeURLがSQLiteファイルの場所。

NSDictionary* sourceMetaData = [NSPersistentStoreCoordinator
                                metadataForPersistentStoreOfType:NSSQLiteStoreType
                                URL:storeURL
                                error:&error];

if (sourceMetaData != nil) {
    NSMutableDictionary *newMetadata = [sourceMetaData mutableCopy];
    newMetadata[@"newMataKey"] = @"newMetaString";
    [NSPersistentStoreCoordinator setMetadata:newMetadata
                     forPersistentStoreOfType:NSSQLiteStoreType
                                          URL:storeURL error:&error];
}

CoreData:SUBQUERY() でSQLite error code:1, ‘no such column:…のエラーへの対処


例えば、データベースのように1つのレコード(record エントリー)に複数のフィールドがあって、フィールドは別のエントリー(fieldエントリー)にして親であるレコードから1対多でリレーションする。フィールドにはカラム番号(colNumber属性)とテキスト(text属性)があるとする。
この場合に、カラム番号とテキストの一部一致をAND条件で指定して検索したい場合は、SUBQUERYを使うやり方がある。SUBQUERY()は @count をとって0でなければヒットしたということで記述するようにするようだ。

そこで、以下のように書いてみた。

NSPredicate*p = [NSPredicate predicateWithFormat:@"SUBQUERY(record.fields, $x, $x.text contains[cd] %@ AND $x.colNumber == %@).@count > 0",searchText, searchColNumber]

ところが、
SQLite error code:1, ‘no such column:…
というエラーが発生してしまう。

どうも、元になるセットの記述にrecord.fieldsのようにドット演算子でつないだものを使えないようだ。
以下のサイトにヒントがあった。
objective c – CoreData many-to-many relation (sub)query – Stack Overflow

以下のように修正して解決。

NSPredicate*p = [NSPredicate predicateWithFormat:@"SUBQUERY(record, $r, (SUBQUERY($r.fields, $x, $x.text contains[cd] %@ AND $x.colNumber == %@)).@count > 0) != NULL",searchText, searchColNumber]

SUBQUERYを二重に記述することもそうだが、それより最後を != NULL にすればよいということが分かるまでが僕にはかなり大変だったのでメモ。