タグ別アーカイブ: iOS

iOS11のデザインって・・

iOS11にしたけど、なんかダサい。
妙に太いフォント、文字のサイズ、UIの細かい所、アイコンの角丸のアール、影の統一感がない。
細かいところがつめきれていないというか、バランスが悪いというか。。

自分の感性が間違ってるのかもしれんけど、Apple製品にあるまじきレベル。
これでOKだすクックがダメだわ。

SwiftでQRコードを生成する

Core Imageを使うと簡単に作れるんですね〜
Swiftで実装するとこんな感じです。

func makeQRCodeImage(code:String) -> UIImage? {    
    guard let ciFilter = CIFilter(name: "CIQRCodeGenerator") else {
        return nil
    }
    ciFilter.setDefaults()
    
    // QRコードを設定
    ciFilter.setValue(code.data(using: String.Encoding.utf8), forKey: "inputMessage")
    
    // 誤り訂正レベルを設定
    // "L" "M" "H" が設定できるみたい
    ciFilter.setValue("M", forKey: "inputCorrectionLevel")
    
    if let outputImage = ciFilter.outputImage {        
    	// 作成されたQRコードのイメージが小さいので拡大する
        let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
        let zoomedCiImage = outputImage.applying(sizeTransform)
        return UIImage(ciImage: zoomedCiImage, scale: 1.0, orientation: .up)
    }
    
    return nil
}

ルビ付きのNSAttributedStringを使うとiOS8でBAD_ACCESSが発生する

原因としてはルビに空文字を設定してるのが悪いっぽい。

たとえばこんな感じ

// 空文字をルビとして設定する
let ruby = ""
var textRef: [Unmanaged<CFString>?] = [Unmanaged<CFString>.passRetained(ruby as CFString) as Unmanaged<CFString>, .none, .none, .none]
let annotation = CTRubyAnnotationCreate(.auto, .auto, 0.5, &textRef[0]!)
                
let attributedString = NSAttributedString(
                    string: "漢字",
                    attributes: [kCTRubyAnnotationAttributeName as String: annotation]

// ↓ ここでEXC_BAD_ACCESSが発生する
let line = CTLineCreateWithAttributedString(attributedString)

 

ほかにも

let framesetter = CTFramesetterCreateWithAttributedString( attributedText)

// ↓ ここでEXC_BAD_ACCESSが発生する
let frame = CTFramesetterCreateFrame(framesetter, CFRange(), path, nil)

ちなみにiOS10じゃ発生しないよ。

[Swift]UITableViewで無限スクロールしてみる

WEBAPIから取得したブログエントリーを無限スクロールで表示するビューアを作成する事になったので、
UITableViewを使って実装のサンプルを作ってみました。

実装の概要
・UITableViewのスクロールに応じて必要なデータを必要なタイミングでロード。
・上にスクロールした場合にはデータを先頭に、下にスクロールした場合にはデータを末尾に追加する
・データの変更時にはUITableViewの表示位置(contentOffset)を調整する
・セルの高さは動的に決定する(行毎の高さはキャッシュする)

表示用のデータはこんなものを想定します。

class MyData
{
    var date:NSDate;
    var text:String;
    
    init(date:NSDate,text:String){
        self.date = date;
        self.text = text;
    }
}

dateが並び順(タイトル)で、表示コンテンツとしてtextがあります。

で、この情報をUITableViewに表示するのですが、今回のサンプルはコンテンツの内容に応じてセルの高さを動的に決定しようと思います。
高さを求めるのはそれなりにコストが高いので、計算した高さを表示用データとペアにして格納するためのクラスを定義します。

class CellData<T> {
    // 表示データ
    var data:T
    // セルの高さ
    var height:CGFloat?
    
    init(data:T,height:CGFloat?){
        self.data = data
        self.height = height
    }
}

セルの高さを求める時には表示用のコードと殆ど同じになりますので、実際のセルに計算させると良いかと。
ですので、こんな感じのUITableViewCellを定義します。

class MyTableViewCell: UITableViewCell
{
    private var titleLbl:UILabel!
    private var textLbl:UILabel!
    
    // 計算済の高さが設定される
    var requiredHeight:CGFloat = 0
    
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String!)
    {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.setupSubviews()
    }
    
    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
        self.setupSubviews()
    }

    func setupSubviews()
    {
        self.titleLbl = UILabel()
        self.titleLbl.font = UIFont.boldSystemFontOfSize(15)
        self.addSubview(self.titleLbl)
        
        self.textLbl = UILabel()
        self.textLbl.font =  UIFont.systemFontOfSize(13)
        self.textLbl.numberOfLines = 0
        self.addSubview(self.textLbl)
    }
    
    // 表示用のデータを設定
    // 必要な高さも求めてrequiredHeightに保存
    func setData(data:MyData)
    {
        titleLbl.frame = CGRectMake(5, 5, self.frame.width - 5 * 2.0, 18)
        titleLbl.text = "\(data.date)"
        
        
        self.textLbl.frame = CGRectMake(5, titleLbl.frame.origin.y + titleLbl.frame.height + 5, self.frame.width - 5 * 2.0, 10)
	// 設定した文字列によってサイズを動的に変更
        self.textLbl.text = data.text
        self.textLbl.sizeToFit()
        
        // 必要な高さを保存しておく
        requiredHeight = textLbl.frame.origin.y + textLbl.frame.size.height + 5
    }

}

setDataでMyDataを渡すと、requiredHeightにセルを表示するために必要な高さが設定されます。

これらのクラスを使ってUITableViewを使います。
コードはこんな感じ!(ズババン!)

class SampleViewController:UIViewController,UITableViewDelegate,UITableViewDataSource {

    // MARK: - Properties
    
    var tableView: UITableView = UITableView()
    
    
    // テーブルに表示するデータを保持する
    // ※SortedList(→コード)を使っていますが、なんでも大丈夫です
    var tableData:SortedList<CellData<MyData>> = SortedList<CellData<MyData>>(compFunc: { (left, right) -> CompareResult in
        switch (left.data.date.compare(right.data.date)){
        case .OrderedSame: return CompareResult.Equal
        case .OrderedAscending: return CompareResult.RightLarge
        case .OrderedDescending: return CompareResult.LeftLarge
        }
    })


    // セルの高さを算出するための作業セル
    var workingCell = MyTableViewCell()
    
    // データ処理の実行フラグ
    var isLoading = false


    // MARK: - Initialize
    
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    


    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = UIColor.yellowColor()
        
        
        // テーブルの設定
        tableView.registerClass(MyTableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
        self.view.addSubview(tableView)
        
    
        // 初期データの読み込み  昨日から30日分のデータを取得して初期データとします
        let date = NSDate(timeInterval: 24 * 60 * 60 * -1, sinceDate: NSDate())
        self.loadDataFromDate(date, count: 30, toFuture: true, containTargetDate: true) {[weak self] (results) in
            
            self?.tableData.append(results)
            self?.tableView.reloadData()
            
            // 指定した日付が先頭に表示されるようにスクロール
            let indexPath = NSIndexPath(forRow: 1, inSection: 0)
            self?.tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: UITableViewScrollPosition.Top)
        }

    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()   
        tableView.frame = self.view.bounds;
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
    }    
    
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
    }


    // MARK: - 表示用データの取得
    
    // データの取得
    // DBやWebAPI使う事を前提に非同期処理としています
    func loadDataFromDate(date:NSDate,count:Int,toFuture:Bool
        ,containTargetDate:Bool,callback:(results:Array<CellData<MyData>>) -> ())
    {
        isLoading = true;
        // データ取得中はウエイトカーソル的なものを表示した方が良いかもね
 
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        let mainQueue = dispatch_get_main_queue()
        weak var weakself = self
        
        dispatch_async(queue,{
            autoreleasepool {
                // ダミーデータを作成する
                var loaded = Array<CellData<MyData>>()
                let dayInterval = Double(24 * 60 * 60 * (toFuture ? 1 : -1))
                
                var dt = containTargetDate ? date : date.dateByAddingTimeInterval(dayInterval)
                
                while loaded.count < count {
                    
                    let csdata = CellData(data: MyData(date: dt, text: weakself!.makeRandomString()), height: .None)
                    
                    loaded.append(csdata)
                    
                    dt =  dt.dateByAddingTimeInterval(dayInterval)
                }
                // 少しウエイトしてみる
                NSThread.sleepForTimeInterval(0.5)
             
                dispatch_sync(mainQueue, {
                    callback(results: loaded)
                    weakself!.isLoading = false;
                })
                
            }
        })
    }
    

    // ランダムな文字列を生成する
    func makeRandomString() -> String {
        let wordTable = ["りんご","梨","ひよこ","ごりら","犬","を食べる","を投げる","を拾う","を蹴る","自分","へ怒る","を割る","コップ","テレビ"];
        let wordCount = arc4random_uniform(50) + 1
        
        let  result = (0..<wordCount).reduce("") { (v, no) -> String in
            let idx = Int(arc4random_uniform(UInt32(wordTable.count)))
            return v + wordTable[idx]
        }
        
        return result
    }
    

    // MARK: - UITableViewDelegate,UITableViewDataSource 
    
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1;
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        
        let obj = tableData[indexPath.row]
        if let height = obj.height {
            return height
        } else {
            // セルの表示サイズを計算してキャッシュする
            workingCell.setData(obj.data)
            let height = workingCell.requiredHeight
            obj.height = height
            return height
        }
    }
    
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)        
        let obj = tableData[indexPath.row]
        
        if let csCell = cell as? MyTableViewCell {
            csCell.setData(obj.data)
        }
        
        return cell
    }

    
    func scrollViewDidScroll(scrollView: UIScrollView) {
        
        // データ取得中は処理しない
        if isLoading {
            return
        }
        
		// 事前読み込みを行う末端からの距離
		let margin:CGFloat = 100
        
        // 下方向への読み込み判定
        // ここの判定に一定のマージンを設ける事で事前読み込みが可能となります
        // このサンプルでは必要となったときにガバッと取得
        let bottomOffset = self.tableView.contentOffset.y + self.tableView.bounds.size.height
		let lengthFromBotton = self.tableView.contentSize.height - bottomOffset        
        if (lengthFromBotton >= margin){            
            let dt = tableData[tableData.count-1].data.date            
            self.loadDataFromDate(dt, count: 10, toFuture: true, containTargetDate: false) {[weak self] (results) in
                self?.tableData.append(results)
                self?.tableView.reloadData()
            }
        }
        

        // 上方向への読み込み判定
        if (self.tableView.contentOffset.y <= margin){            
            let dt = tableData[0].data.date            
            self.loadDataFromDate(dt, count: 5, toFuture: false, containTargetDate: false) {[weak self] (results) in
   		 // 上方向へのスクロールの場合にはスクロール位置の調整が必要
                var height:CGFloat = 0
                for v in results {
                    // セル毎の高さを求めるつつ、tableDataへ格納する
                    self!.workingCell.setData(v.data)
                    v.height = self!.workingCell.requiredHeight
                    height += v.height!                    
                    self?.tableData.append(v)
                }
                
                // 取得したセルの高さ(合計)だけスクロール位置を移動させる
                self?.tableView.contentOffset.y = self!.tableView.contentOffset.y + height
                
                self?.tableView.reloadData()
            }
            
        }
        
    }

}

ポイントは上方向にスクロール(先頭方向)した場合のスクロール位置の調整ですかね。
下方向にスクロールした場合にはデータを末尾に追加するのでスクロール位置は変更しなくて良いのですが、上方向の場合には今表示している位置より前にデータを追加することになります。
ですので、今の表示位置を読み込んだセルの高さ分だけ下に調整してあげる必要があります。

このサンプルではMyDate.dateで勝手にソートされるようにSortedListを使っていますが、普通のArrayでも大丈夫です。
上にスクロールするときは先頭にデータを追加、下にスクロールした時は末尾に追加すれば良いのです。

MKMapViewのコンパスを移動させる

MapViewを回転(方角を変更)すると右上にコンパスが表示されます。
こんなの↓
20150727_1

右上に表示されるもんですから画面上部にツールバーをフロートで表示させようとした時などにとっても邪魔!
しかもこれ、iOS9だと消せるらしいのですがiOS8だと消せないんです(たぶん)。
で、移動させちゃえって思ったのですが、そんなプロパティ見つかりませんし、、

ただ、一部のアプリでは標準と違う場所に表示されているので何かしら方法があるのではないかと思って色々調べていたらstackoverflowでこんな情報が見つかりました。

http://stackoverflow.com/questions/18903808/ios7-compass-in-mapview-placing

簡単に説明すると

  • MKMapviewはUILayoutSupportが示す位置を参考にしてコンパスとか著作権情報などの表示位置を決めている。
  • で、このUILayoutSupportってのはViewControllerのtopLayoutGuideとかbottomLayoutGuideで決める事ができる。
  • なので、いい感じの位置情報を返すUILayoutSupportを、topLayoutGuideとかbottomLayoutGuideで返せばOK!

 
 
実際のコードはこんな感じで

最初に UILayoutSupport を実装したクラスを定義します

class MapLayoutGuide: NSObject,UILayoutSupport {
    
    private var _length:CGFloat = 0;
    
    init(length:CGFloat){
        super.init();
        _length = length;
    }
    
    var length: CGFloat {
        get {
            return _length
        }
    }

    @available(iOS 9.0, *)
    var topAnchor: NSLayoutYAxisAnchor {
        return NSLayoutYAxisAnchor()
    }
    
    @available(iOS 9.0, *)
    var bottomAnchor: NSLayoutYAxisAnchor {
        return NSLayoutYAxisAnchor()
    }

    @available(iOS 9.0, *)
    var heightAnchor: NSLayoutDimension {
        return NSLayoutDimension()
    }
}

で、これを ViewController の topLayoutGuide で返すようにオーバーライド

override var topLayoutGuide: UILayoutSupport {
   get {
      return MapLayoutGuide(length: 50);
    }
}

するとコンパスが下図のように移動してくれます。

20150727_2

(2015.09.15) iOS9向けのコードを追加しました

NSStringからnull文字を削除

デバイス制御系の処理を書いているとデバイスのSDKから渡されたNSStringが変な動きをすることがあります。
stringWithFormatで処理しても途中で切れたり、NSMutableStringのappend系で追加しても切れたり。。
私が直面した現象の原因は単純で、null文字がNSStringに含まれていることなんですけど、なんつーかFucking!ですよね。

いくら舌打ちしたところでSDKを修正してもらえるわけではありませんので、対応を。

下記のコードでNSStringに含まれているnull文字を削除できます。

-(NSString*)trimNullString:(NSString*)val
{
  if (!val) return nil;

  NSInteger length = [val length];
  unichar buffer[length];
  [val getCharacters:buffer range:NSMakeRange(0, length)];

  int endpos = 0;
  for (int i=0; i<length; i++) {
    unichar c = buffer[i];
    if (c == 0x0){
      endpos = i;
      break;
    }
  }

  if (endpos > 0){
    NSString *result = [NSString stringWithCharacters:(const unichar *)buffer  length:(NSUInteger)endpos];
    return result;
  } else {
    return @"";
  }
}

iOS8で NSDecimalNumber が変な値を返してくる!(怒)

iOS8上でなぜか変な挙動を示すアプリがあったので原因を調査してみたところ、NSDecimalNumber の integerValue や longValue が変な値を返すようになっていた。

検証コード

NSDecimalNumber* n = [NSDecimalNumber decimalNumberWithString:@"123.123456789012345678"];
if ([n intValue] !=[n integerValue]){
    NSLog(@"What?!");
    NSLog(@"decimal %@", n);           
    NSLog(@"int %d", [n intValue]);
    NSLog(@"integer %ld",[n integerValue]);
    NSLog(@"long %ld", [n longValue]); 
    NSLog(@"longlong %lld", [n longLongValue]);
    NSLog(@"uinteger %ld",[n unsignedIntegerValue]);
 } else {
    NSLog(@"success!!");
 }

このコードを実行すると iOS7.1(64bit)では次のように素直な値が表示されます。

decimal 123.123456789012345678
int 123
long 123
longlong 123
integer 123
uinteger 123

でもこれがiOS8だと・・
64bit環境では
decimal 123.123456789012345678
int 123
integer -6
long -6
longlong -6
uinteger 12

32bit環境では
decimal 123.123456789012345678
int 123
integer 0
long 0
longlong -6
uinteger 0

って結果となる。なんでやねん!!

NSDecimalNumberを”123.123456″とかで初期化するとこの問題は発生しないんだよね。
桁数の問題であれば NSDecimalNumber を初期化する際にまるめておけば(有効桁数が十分なのであれば)解決するんだけど、どうだんんだろ?

なんにせよ、iOS8のバグだよね。きっと。

XCode6-beta2で作ったAD-Hoc用ipaファイルがインストール出来ない件

まぁ表題どおりなんですけど。
itunesからインストールしようとしても、WEBからインストールしようとしてもダメなんです。
なんでかな〜とGoogle先生にたずねていたらこんな方法で対応できるようです。

/adhoc_release/testapp.ipa がXCodeから出力されたipaファイルだとすると
(パスは適当に権限のあるところでね)

ditto -xk /adhoc_release/testapp.ipa /adhoc_release/temp
ditto -ck –norsrc /adhoc_release/temp /adhoc_release/testapp_repacked.ipa
rm -r /adhoc_release/temp

これで出力された testapp_repacked.ipa を testapp.ipa にリネームしてインストールするとOK

[Swift] Rangeの罠

RangeのendIndexと聞いてどのような意味を思い浮かべるでしょうか?
0から4のRangeはどの範囲を示しているでしょうか?

今回はRangeのendIndexについて説明します。

forループする時に下記のように記述すると思います

for i in 0..<4 {
  // 0〜3までループ
} 

この時 0..<4は Arange<Int>を示しており、下記のrangeと同じ意味です
let range = Range<Int>(start:0,end:4)
※ ちなみに 0…4であれば Range<Int>(start:0,end:5) です。

このrangeのstartIndexとendIndexを調べると下記のようにイニシャライザで指定した値がそのまま入っています。

println("range \(range.startIndex)〜\(range.endIndex) ")
// range 0〜4

もちろんこのように生成したrangeをfor-inでループしてもリテラルで宣言したものと同様に
処理されます。

for i in range {
  // 0〜3までループ
}

で、何が言いたいのかと言うと、、
endIndexが4ならループ変数 i が4まで実行させるべきではないかと。
そのほうが分かりやすいよね。
javaのString.substringのendIndexと同じって思えば良いんだけど、そもそもjavaのそれも気に入らないので。。

 

百歩ゆずってループで使う場合にはこの仕様で良いとシヨウ(仕様と掛かってます/ドヤ)
でも何かしらの範囲を示す場合に混乱しますよね。
例えばこのような関数があったとして

// ティーンエイジの範囲を求める
func teenagerRange() -> Range<Int> {
  // ティーンって13才から19才だよね
  return Range<Int>(start:13,end:19)
}

この関数の戻り値を使ってティーンエイジャーを列挙すると

for age in teenagerRange() {
  print("age \(age)")
}

・・13から18でループされる、ナインティーンどこいった?

 
 

んー、まぁ1万歩ゆずって整数ならまだ良しとしよう。
DoubleとかでもRangeを作れるので

let range = Range<Double>(start:0.0,end:1.0) 

この場合、最終の値はなに?
0.9? 0.999999?

→ ForwardIndexTypeに準拠しないためDoubleのRangeは使えなくなりました。

もちろん (range.startIndex <= X && X < range.endIndex) という条件が正の場合とすれば良いのは分かるし、
この仕様の方がフィットする型があるのもわかります。(日時とかね)
でも 0 〜 0.9(just)までを示す事が出来ないと思いませんか?

Rangeってからには範囲を示すもので汎用的に使いたいんですけど、なんか使いづらい!
というか、メソッドによってRangeの意味が変わってしまう気がして気持ち悪い!
上限値、下限値を範囲に含めるか否かを示す値があれば良いんだけど、これが無いからだよね。
これは要注意だよ!

(2014-07-11) XCode6.0-beta3での仕様変更を反映

[Swift] Sequence実装のサンプル(独自クラスのリスト)

SequenceTypeを実装することによって独自クラスをfor-inなどでループ処理することが可能になります。

 
SequenceTypeプロトコルは
func generate() -> GeneratorType
だけを実装すれば良いのですが、
今回のサンプルでは二分探索(バイナリサーチ)によるジェネリックスなSortedListを実装してみました。
 
 

使い方はこんな感じです

// イニシャライザで比較関数を渡す
// この例ではクロージャを使っています
let sortedList = SortedList<Int>(){
    (left:Int,right:Int)-> CompareResult in
        if (left == right) {
            return .Equal
        } else if (left > right) {
            return CompareResult.LeftLarge
        }
        else {
            return CompareResult.RightLarge
        }
    }

// 要素を追加
sortedList.append(7)
sortedList.append(5)
sortedList.append(3)

// 配列からも追加
sortedList.append([1,2,9])

// for-inループで要素を列挙
// Sequenceプロトコルが使われています
for elm in sortedList {    
	println("elm:\(elm)")
}
// 追加した要素がソートされて出力されます 1,2,3,5,7,9 


// 要素のインデックスを求める
if let idx = sortedList.indexOf(4) {
	println("found index=\(idx)")
} else {
	println("not found.")
}

 
 

以下ソース

// 要素の比較結果を示すEnum
enum CompareResult {
    case Equal,LeftLarge,RightLarge
}

// SortedListの実装
class SortedList<T>:SequenceType {
    
    // データの保存先 (実装されたらprotectedにするよ)
    var data = Array<T>()
    
    // 比較関数 (実装されたらprotectedにするよ)
    var compFunc:(left:T,right:T) -> CompareResult
    
    
    // イニシャライザ
    init(f:(left:T,right:T)-> CompareResult){
        self.compFunc = f
    }
    
    convenience init(_ initialData:Array<T>, f:(left:T,right:T)-> CompareResult){
        self.init(f:f)
        self.append(initialData)
    }
    
    // Sequence.generateの実装
    // これを実装することでfor-inで反復処理できるようになるよ
    func generate() -> IndexingGenerator<T[]> {
    	// Arrayの同機能を使って簡単に実装してますがカスタムについては後述
        return self.data.generate()
    }
    

    // ここからはArrayに実装されている機能を外にインターフェース

    subscript(index: Int) -> T {
        get {
            return data[index]
        }
        set {
            data[index] = newValue
        }
    }
    
    subscript(range: Range<Int>) -> ArraySlice<T> {
        get {
            return data[range]
        }
        set {
            data[range] = newValue
        }
    }
    
    // 要素を追加する
    // 追加されたIndexを返す
    // すでに同じ値が登録されていた場合にはNoneを返す
    func append(newElement:T) -> Int? {
        if (self.data.isEmpty){
            self.data += newElement
            return 0
        } else {
            let (hit,idx) =  _insertPos(newElement)
            if hit {
				// 上書きするのであればここで
                return .None
            } else {
                data.insert(newElement,atIndex:idx)
                return idx;
            }
        }
    }
    
    // 要素の一括登録
    func append(newElements:Array<T>) {
        let rate = self.data.isEmpty ? 1.0 : Double(newElements.count) / Double(self.data.count)
        if rate > 0.5 {
            //総量の5割を超えるレコードを追加する場合は一括でappendしてあとでソートする
            data += newElements
            _sortData();
            
        } else {
            self.data.reserveCapacity(self.data.count + newElements.count)
            for elm in newElements{
                self.append(elm)
            }
        }
    }    
    
    func append(newElements:SortedList<T>){
        self.append(newElements.array)
    }
    
    func removeAtIndex(index: Int) -> T {
        return data.removeAtIndex(index)
    }

    func removeLast() -> T {
        return data.removeLast()
    }
    
    func removeAll(keepCapacity: Bool = false) {
        data.removeAll(keepCapacity: keepCapacity)
    }
    
    // 要素を削除する
    // 戻り値:削除した要素のIndexを返す
    func remove(elm:T) -> Int? {
        let (hit,idx) =  _search(elm)
        if (hit){
            data.removeAtIndex(idx)
            return idx
        } else {
            return Optional.None
        }
    }

    var array:Array<T> {
        get{
            return self.data
        }
    }
    
    
    var count: Int {
    	get {
        	return data.count
    	}
    }
    
    var isEmpty: Bool {
    	get {
        	return data.isEmpty
    	}
    }
    
    var capacity: Int {
    	get {
        	return data.capacity
    	}
    }
    
    func reserveCapacity(minimumCapacity: Int) {
        data.reserveCapacity(minimumCapacity)
    }

    func filter(includeElement: (T) -> Bool) -> SortedList<T> {
        return SortedList(data.filter(includeElement),f:self.compFunc)
    }
    
    // (mapは比較関数も変わってしまうので実装しないことにしよう..)


    func reduce<U>(initial: U, combine: (U, T) -> U) -> U {
        return data.reduce(initial, combine)
    }
    

    // ここからは独自の機能
    
    // 指定された要素を検索してデータのインデックスを求める
    func indexOf(elm:T) -> Int? {
        let (hit,idx) =  _search(elm)
        return hit ? idx : Optional.None
    }
    
    
    // 指定したインデックスの範囲の要素をもったSortedListを返却する
    func sublist(range:Range<Int>) -> SortedList<T> {
        let subdata = data[range]
        return SortedList(Array(subdata),f:self.compFunc)
    }


 	// ここからはprivate or protectedにしたいな(実装されれば)
    
    
    // 要素の位置を検索する
    // 戻り値
    //  .0:存在したか
    //  .1:Hitした場所 or 近かった場所(前後のどちらか)
    func _search(elm:T) -> (Bool,Int) {
    
        var l = 0
        var r = self.data.count-1      
        
        while l < r {
            let m = (l + r) / 2;
            
            switch self.compFunc(left:elm,right:data[m]) {
            case .LeftLarge://右を探索
                l = m + 1
            case .RightLarge://左を探索
                r = m - 1
            case .Equal:
                return (true,m)
            }
        }
        
        if self.compFunc(left:elm,right:data[l]) == .Equal {
            return (true,l)
        } else {
            return (false,l)
        }
    }
    
    // 要素をインサートする場所を見つける
    func _insertPos(elm:T) -> (Bool,Int){
        
        let (hit,idx) =  _search(elm)
        if (hit) {
            return (hit,idx);
        }
        else {
            switch self.compFunc(left:elm,right:self.data[idx]) {
            case .LeftLarge:
                //追加する要素が大きい・・・idxの後にインサート
                return (false,idx+1)
            default:
                return (false,idx)
            }
        }
    }
    
    // 一括でソートする
    func _sortData() {
        sort(data){
            (o1 : T, o2 : T) -> Bool in
            return self.compFunc(left:o1,right:o2) == .RightLarge
        }
    }
}

C#のSortedListのように使用する場合にはジェネリックス型としてタプルを使えばよいかと。
 
 

今回行ったSequenceTypeプロトコルの実装はArrayの同機能を使って簡単に実装したのですが、カスタムしたい場合には下記のようにします。 
 
方法1. GeneratorOf を使う場合

// generateを下記のようにすればループする順序をカスタマイズできます
// ※カスタマイズしたらSortedListでは無くなってしまいますが。
func generate() -> GeneratorOf<T> {
    var index : Int = 0
    return GeneratorOf<T> {
        if (index < self.data.count){
            return self.data[index++]
        } else {
            return .None
        }
    }
}

 

方法2. 独自のGeneratorを実装する場合

// こんな感じでGeneratorを定義して
class SortedListGenerator<T> : Generator {
    let list : SortedList<T>
    var index : Int
    
    init(_ list: SortedList<T>) {
        self.list = list
        index = 0
    }
    
    mutating func next() -> T? {
        if index >= list.count { return .None }
        return list[index++]
    }
}

// 独自クラスのgenerate()でこのインスタンスを返すようにする
func generate() -> SortedListGenerator<T> {
    return SortedListGenerator(self)
}

 
 

今回のサンプルではSequenceTypeプロトコル以外の機能も実装したのでちょっと長いですが、
SequenceTypeプロトコル自体は簡単に実装できます。
独自クラスを反復処理する場合にはぜひ実装してみて下さい。