Amarronの日記

iOSやMac、Web系の記事を書きます。

CoreData の 関連図 と 手順

概要

  • CoreDataの知識が曖昧だったのでクラスの関連図や手順についてのメモです(細かい実装方法や使い方は他の参考サイトを参考にしてください)
  • DB自体初めての人には登場人物が多くて難易度高めな気がします
  • 参考資料:iOS Core Data チュートリアル

関連図

f:id:Amarron:20150121224913p:plain

Core Data RDB
NSManagedObjectModel DB
Entity Table
NSManagedObject Record
Attribute Column
  • NSManagedObjectModel:管理オブジェクトモデル(データベース)

f:id:Amarron:20150121224955p:plain

  • NSManagedObject:管理オブジェクト(レコード)
  • NSManagedObjectContext:管理オブジェクトコンテキスト(オブジェクト空間、管理オブジェクトのコレクションを管理する)

f:id:Amarron:20150121225004p:plain

  • NSPersistentStoreCoordinator:永続ストアコーディネータ(ストアを管理して、管理オブジェクトコンテキストに、1つ の統一されたストアのファサードを提供する)

f:id:Amarron:20150121225014p:plain

作成手順

  1. プロジェクトの準備
    • Xcodeで、「iOS」セクションの「Window-basedApplication」テンプレーを使用してプロジェクトを作成します。オプション(Options)(Options)セクションで、ストレージにCore Dataを使用するスイッチ(「Use Core Data for storage」)を選択
      • AppDelegateクラスにCoreData用のプログラムが追加され、【プロジェクト名】.xcdatamodeldが作成される
  2. テーブルの作成
    • Xcodeのデータモデリングツール(プロジェクト名】.xcdatamodeld)を使用して、新規エンティティを作成
    • f:id:Amarron:20150121225041p:plain

    • そのエンティティを表すカスタムクラス(NSManagedObject)を作成(xcdatamodeldを選択し、「Create NSManagedObject」で作成可能)

  3. テーブルにデータ追加
    • NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
    • NSEntityDescriptionでテーブルの指定
    • NSManagedObjectで値の受け渡し
  4. テーブルからデータ取得
    • NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
    • NSFetchRequestでテーブルの指定
import CoreData

class MyClass : UIViewController {
// 3. テーブルにデータ追加
@IBAction func insert(sender: AnyObject) { 
        let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let managedContext: NSManagedObjectContext = appDelegate.managedObjectContext!
        
        /* Create new ManagedObject */
        let entity = NSEntityDescription.entityForName("テーブル名", inManagedObjectContext: managedContext)
        let personObject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
        // let myPersonObject = NSManagedObjectを継承したカスタムクラス(entity: entity!, insertIntoManagedObjectContext: managedContext) // 自分で作成したカスタムを使う場合
        
        /* Set the name attribute using key-value coding */
        personObject.setValue("値", forKey: "カラム名")
        // myPersonObject.カラム名 = "値" // 自分で作成したカスタムを使う場合
        
        /* Error handling */
        var error: NSError?
        if !managedContext.save(&error) {
            println("Could not save \(error), \(error?.userInfo)")
        }
        println("object saved")
}

// 4. テーブルからデータ取得
@IBAction func selectAll(sender: AnyObject) { 
        /* Get ManagedObjectContext from AppDelegate */
        let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let manageContext:NSManagedObjectContext = appDelegate.managedObjectContext!

        /* Set search conditions */
        let fetchRequest = NSFetchRequest(entityName: "テーブル名")
        var error: NSError?
        
        /* Get result array from ManagedObjectContext */
        let fetchResults = manageContext.executeFetchRequest(fetchRequest, error: &error)
        if let results: Array = fetchResults {
            for obj:AnyObject in results {
                let name:String? = obj.valueForKey("カラム名") as? String
                println(name)
            }
            println(results.count)
        } else {
            println("Could not fetch \(error) , \(error!.userInfo)")
        }
}

まとめ

  • xcdatamodeldでテーブル情報(テーブル名、カラム名、カラム属性、リレーション等)を設定する
  • NSManagedObjectを継承したクラスをテーブルのカラム名を書いて、テーブルとの受け渡し用の入れ物に使う
  • NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
  • NSEntityDescriptionでテーブルの指定
  • データの保存場所を確認したい場合、applicationDocumentsDirectoryを見る
  • 個人的には、FMDBのライブラリを使って直接SQLでデータをやりとりした方が自由で楽なきがします。。。

参考サイト

Swift 文法

f:id:Amarron:20141231160353j:plain

概要

Swiftの入門的な部分をまとめてみました。
初心者向けに文法の部分を中心に書きました。
また、SwiftObjective-Cより直感的ですっきりしているので、
iOSの開発を始めたい方はSwiftでの開発がオススメです。

目次

  1. 変数宣言(var,let)
  2. 型(Int,String...)
  3. 文字列操作
  4. 配列(Array)
  5. 辞書(Dictionary)
  6. タプル(Tuple)
  7. 繰り返し(for,while)
  8. 条件分岐(if,switch)
  9. 関数(func)
  10. クラスと構造体(class,struct)
  11. オプショナル型(Optional)
  12. おすすめの勉強法

1. 変数宣言(var,let)

  • 変数の宣言には「var」と「let」がある
    • varは、変数
    • letは、定数
// 型を指定して生成
var varInt:Int      // 宣言:var 変数名:型
varInt = 0          // 代入:変数名=値
var varInt2:Int = 0 // 宣言+代入:var 変数名:型=値
// 型推論(型を指定せずに生成)
var varInt3 = 3     // 値からInt型の変数が生成される

let pi = 3.14
// let pi2:Double   // 宣言のみだとエラー(値の代入も宣言時に必要)
// pi = 3.1415926   // 値を変更するとエラー

2. 型(Int,String...)

  • マイナス値がない「UInt」型がある
    • 添字などで使うと安全性が高まる
  • 型指定なしの「Any」型がある
名前 説明
Int 整数型
UInt 符号なし整数型
Float 単精度少数型
Double 倍精度少数型
Bool 論理型
Charecter 文字型
String 文字列型
Any 型指定なし
AnyObject 型指定なし(Classに限定)

3. 文字列操作

  • Stringの文字列は「+」で結合できる
    • var str:String = "Hello"+"World"
  • String内に処理の実行結果を代入する時は「\(処理)」で出来る
    • var str:String = "1+1=\(1+1)"

4. 配列(Array)

// 型を指定
var varArray:[String]
// 型推論(型を指定せずに生成)
var varArray2 = ["var0", "var1", "var2"]
// 空の配列
var varArray3 = [String]()
var varArray4:[String] = Array() 
// 要素の追加
varArray2.append("var3")

5. 辞書(Dictionary)

// 型を指定
var varDict:Dictionary<String, String>
// 型推論(型を指定せずに生成)
var varDict2 = ["key0":"var0", "key1":"var1", "key2":"var2"]
// 空の辞書
var varDict3 =  Dictionary<String, String>()
var varDict4:Dictionary<String, String> = Dictionary()
// 要素の追加
varDict2["key3"] = "var3"
// 値の取得
if let value = varDict2["key3"] {
    print(value)
}
// キーと値の取得例
for (dictName, dictValue) in varDict2 {
    print("キーは\(dictName)で、値は\(dictValue)です。")
}

6. タプル(Tuple)

  • タプルは複数の値をグループとして扱える(「x,y,width,height」や「name,age」とかをグループにしたい時に便利)
  • タプル内の値は、異なる型の値を設定出来る
// 値を設定
let http404Error = (404, "Not Found")
// 値を取得
print("The status code is \(http404Error.0)")

// 他の変数に設定、取得
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 変数名をつけて設定、取得
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")

7. 繰り返し(for,while)

var roopArray = ["var0","var1","var2"]
// for-in ループ
for i in 0..<roopArray.count {
    print(roopArray[i])
}
// 条件文付きforループ
for var i = 0; i < roopArray.count; i++ {
    print(roopArray[i])
}
// 代入forループ
for value in roopArray {
    print("\(value)")
}
// while
var roopIndex = 0
while roopIndex < roopArray.count {
    print(roopArray[roopIndex])
    roopIndex++
}

8. 条件分岐(if,switch)

  • ifの条件式に()は、書かなくてもOK
  • switchは、breakを書かなくてもbreakする
  • switchでも、条件が複数指定できる
  • switchでも、細かい条件も指定できる
var varStr:String = "Hello World"
// if
if varStr != "" {
    print(varStr)
}
// if条件分岐+代入
var varStr2:String?
if let varStr3 = varStr2 {
    print(varStr3) // varStr2がnilじゃない場合、varStr3にvarStr2を代入
} else {
    print("varStr2 is nil")
}

// if条件分岐+代入(三項演算子) その2
var varInt:Int = 1
var varStr3:String = (1 == varInt) ? "1" : ""

// switch
let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress": // 複数条件
    let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"): // 細かい条件
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

9. 関数(func)

  • 戻り値が複数設定出来る
// 関数を呼び出す
// func 関数名(引数: 型) -> 戻り値
func square(num: Int) -> Int {
    return num * num
}
// 関数を使う
print("計算結果は\(square(2))です")
// 引数が複数の場合、引数を「,」区切りで宣言
func sum(num1: Int, num2: Int) -> Int {
    return num1 + num2
}
// 戻りが複数の場合、戻り値を「,」区切りで宣言
func tax(num: Float) -> (five: Float, eight: Float) {
    return (five: num*1.05, eight: num*1.08)
}
var taxPrice = tax(100.0)
// 複数の戻りを取り出す場合、「.」で指定する
print("消費税5%だと、\(taxPrice.five)円です")
print("消費税8%だと、\(taxPrice.eight)円です")

10. クラスと構造体(class,struct)

  • クラスと構造体は、変数や関数等をまとめたプログラム
  • クラスと構造体の違いは、「型」として性質(セマンティクス)が異なる
    • クラス(class):参照型(コピーした時に、コピー元の値を変更するとコピー先も同じものを参照しているので値が変わる)
    • 構造体(struct):値型(コピーしてもそれぞれにインスタンスされる)

クラス(class)

  • 本格的に作る場合に向いている
    • 継承を使える
    • ARCが使える
  • インスタンスのライフサイクルが長いもに向いている
  • 例)UIColor,UIImage等(殆どがクラス)
class RectClass {
    // 変数
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0
    
    // 関数
    func maxX() -> Float {
        return x+width
    }
    func maxY() -> Float {
        return y+height
    }
}

var rectC = RectClass()
rectC.x = 0;
rectC.width = 100;
print("最大幅は\(rectC.maxX())です");

構造体(struct)

  • 軽く作る場合に向いている
  • プロパティ数が少ないもに向いている
  • 例)CGRext,CGPoint,CGSize,Array等
struct RectStruct {
    // 変数
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0
    
    // 関数
    func maxX() -> Float {
        return x+width
    }
    func maxY() -> Float {
        return y+height
    }
}

var rect0 = RectStruct()
var rect1 = RectStruct(x:0.0, y:0.0, width:100.0,height:200.0)
print("最大幅は\(rect1.maxX())です");

11. オプショナル型(Optional)

  • オプショナル型
    • 「?」をつける
    • nilになる可能性があることを示している
    • ラップされた状態
  • 非オプショナル型
    • 「!」をつける
    • nilになる可能性がないことを示している
    • アンラップされた状態
  • オプショナル連鎖
    • オプショナル型のメソッドや変数にアクセスする時は、オプショナルでアクセスする必要がある
// NSURLクラスでの例
NSURL.h
var scheme: String? { get }
var host: String? { get }
var path: String? { get }
var query: String? { get }

MyClass
var url = NSURL(string: "http://www.apple.com")!
var host: String? = url.host // ラップ
var host2: String = url.host! // アンラップ
// var host3: String = url.query // クエリーを設定していないので実行時エラーになる(宣言元にラップ状態の明示が必要)
var host3: String? = url.query
// host.hasPrefix("www") // オプショナル連鎖(?が必要)
host?.hasPrefix("www")

12. おすすめの勉強法

  1. Swift - Overview - Apple Developerをざっくり読む
  2. The Swift Programing Languageを読む(Swift言語ブログが日本語で解説されていて分かりやすい)
    • A Swift Tour: Playground fileからSampleCodeをダウンロード(1.The Swift Programing Languageがplaygroundで閲覧出来る)
    • 気になる点をplaygroundで書く
  3. Using Swift with Cocoa and Objective-Cを読む(CocoaObjective-Cswiftの違いが書かれている)
  4. オススメの書籍

文法もわかりやすく、rssアプリを作成出来ます。基礎の入門書としてオススメです。

他に参考になる資料(2015.12追記)

その他

  • IBM Swift Sandbox(swiftがウェブプラザで動かせるサイト。マックを持ってないけど軽く触ってみたい人にオススメ。)

Web系 システム 開発 セキュリティ 備忘録

f:id:Amarron:20141124135656j:plain

概要

ウェブアプリケーションのセキュリティの基礎や入門的な部分をざっくり書き出しました。
ログイン機能を使う場合やインフラよりのセキュリティは書いていません。

  1. 脆弱性の原因と攻撃パターン
  2. 脆弱性一覧
  3. 入力処理の脆弱性
  4. 表示処理の脆弱性
  5. SQL脆弱性
  6. まとめ

1. 脆弱性の原因と攻撃パターン

脆弱性が生まれる理由

  1. バグによるもの
  2. チェック機能不足によるもの
    • ディレクトリ・トラバーサル等

能動的攻撃と受動的攻撃

  • 能動的攻撃:攻撃者がwebサーバーに対して直接攻撃すること(SQLインジェクション等)
  • 受動的攻撃:攻撃者がwebサーバーに対して直接攻撃せず、Webサイトの利用者に罠を仕掛ける
    • 単純な受動的攻撃:ユーザーが怪しいサイト閲覧してウィルスに感染
    • 正規サイトを悪用する受動的攻撃:攻撃者がwebサーバーに対して不正操作をして、ユーザーが正規サイトに訪れるとウィルスに感染
    • サイトをまたがった受動的攻撃:①ユーザーが罠サイトを閲覧②ユーザーが正規サイトに攻撃のリクエスト送信③正規サイトからJSなどの仕掛けを含むレスポンスが返却

2. 脆弱性一覧

  • 脆弱性には出力に起因するものと処理に起因するものがある
  • 出力に起因する脆弱性には「インジェクション」という単語がつくものが多い
    • インジェクションとは、「"」「'」等の終端マークを混入させ処理や出力の構造を変化させる行為
# 処理の流れ
クライアントブラウザ
   ↓ HTTPリクエスト
サーバー(入力値検証→処理→出力)
   ↓ HTTPレスポンス
クライアントブラウザ

機能と脆弱性の対応

処理 出力 対応アプリ 発生する脆弱性の種類 起因する脆弱性
処理 表示/HTML ブラウザ クロスサイト・スクリプティング
HTTPヘッダ・インジェクション
出力
処理 DB/SQL RDB SQLインジェクション 出力
処理 外部コマンド シェル OSコマンド・インジェクション 出力
処理 メール メール メールヘッダ・インジェクション 出力
処理 ファイル ファイル ディレクトリ・トラバーサル 処理
重要な処理 - - クロスサイト・リクエストフォージェリ 処理
認証 - - セッションフィクセーション 処理
許可 - - 認可不備 処理

インジェクション系脆弱性

脆弱性 インターフェース 悪用手口 データの終端
クロスサイト・スクリプティング HTML JS等の注入 < " など
HTTPヘッダ・インジェクション HTTP HTTPレスポンスヘッダの注入 改行
SQLインジェクション SQL SQL命令文注入 ' など
OSコマンド・インジェクション シェルスクリプト コマンドの注入 ; | など
メールヘッダ・インジェクション sendmailコマンド メールヘッダ、本文の注入・改変 改行

3. 入力処理の脆弱性

  • Webアプリケーションの入力には、HTTPリクエストとしてわたらせるパラメータ(GET,POST,cookieなど)がある
  • 入力処理には次の3つの検証・変換が必要
    1. 文字エンコーディングの妥当性検証
    2. 文字エンコーディングの変換(必要な場合のみ)
    3. パラメータ文字列の妥当性検証(アプリケーションの仕様に基づいた検証)
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力| ←ここ
|処理|
|出力|
-----
  ↓ HTTPレスポンス
クライアントブラウザ

文字エンコーディングの変換

言語 自動変換 スクリプト記述
PHP pjp.iniなど mb_convert_encoding
Perl - Encode::decode
Java setCharacterEncoding Stringクラス
ASP.NET web.config -

4. 表示処理の脆弱性

  • 表示処理が原因で発生する問題には次の2つがある
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力|
|処理|
|出力|
-----
  ↓ HTTPレスポンス
クライアントブラウザ ←ここ

クロスサイト・スクリプティング

  • クロスサイトスクリプティングとは、ウェブページの部分をユーザからの入力をそのままエコーバック(おうむ返し)することによって生成しているアプリケーションのセキュリティ上の不備を利用して、サイト間を横断して悪意のあるスクリプトを注入する攻撃のこと
  • クロスサイト・スクリプティングではユーザーに対して次のような被害がある
    • 攻撃者のスクリプトでクッキー値を盗まれる
    • 攻撃者のスクリプトでWebアプリケーションの機能を悪用される
    • 偽の入力フォームでのフィッシングで個人情報が盗まれる

クロスサイト・スクリプティング対策

  • 必須対策(個別):HTMLの要素内容について[<]や[&]をエスケープする、属性値は「"」で囲い[<]や[&]をエスケープする
  • 必須対策(共通):HTTPレスポンスに文字エンコーディングを明示する
  • 保険的対策:入力値検証、クッキーにHttpOnly属性を付与、TRACEメソッドを無効

5. SQL脆弱性

  • SQL脆弱性にはSQLインジェクションがある
  • SQLインジェクションとは、SQLの呼び出しに不備がある場合発生する脆弱性SQLが開発者の意図しない形にに改変され実行されること
  • SQLインジェクション脆弱性がある場合、次のような影響を受ける可能性がある
    • データベース内の全ての情報が盗まれる
    • データベースの内容が書き換えられる
    • 承認を回避される(IDとパスワードを用いずにログインされる)
    • その他、データーバースサーバー上のファイルの読み書き、プログラム実行などが行われる
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力|
|処理|
|出力| ←ここ
-----
  ↓ HTTPレスポンス
クライアントブラウザ

脆弱性が生まれる原因

文字列リテラル

  • SQLの標準では、文字列リテラルは「'」で囲む
  • 「'」を使いはみ出した部分で脆弱性が生まれる
# 文字列リテラルの例(BOOKSテーブルから著者がO'Reillyのデータを取得)
SELECT * FROM BOOKS WHERE AUTHOR = 'O'Reilly'
'O'が文字列リテラルで、Reilly'が文字れるリテラルからはみ出した部分
(「SELECT * FROM BOOKS WHERE AUTHOR = 'O';DELETE FROM BOOKS;」みたいな感じで悪用できる)

数値リテラル

  • 文字列リテラル同様、「;」で区切りそのあとに意図しない形のSQLを実行できる
# 数値リテラルの例
SELECT * FROM BOOKS WHERE MONEY <= 20000;DELETE FROM BOOKS
20000が数値リテラル、それ以降が脆弱性を含む意図しない形のSQL

対策

  • プレースホルダSQL分の「?」に値が代入されるやつ)でSQLを組み立てる
  • アプリケーション側でSQLを組み立てる際にリテラルを正しく構成するなどSQL文を変更されないようにする

6. まとめ

  • 入力値のインジェクション対策はかならず行う(「"」「'」「;」等を含んだ入力値でテストする)
  • HTMLの要素内容について[<]や[&]をエスケープする
  • HTTPレスポンスに文字エンコーディングを明示する
  • 入力値検証、クッキーにHttpOnly属性を付与、TRACEメソッドを無効
  • おすすめ書籍

セキュリティの事がひと通り書いてあり、サイトを作成する前に考慮すべき考え事がわかりやすく書いてあります。

iOS SNS シェア 実装方法

概要

SNS等のシェア機能の実装方法の説明です。 実装内容はシェアボタン押下時にアクションシートを表示し、シェアしたい内容を選びシェアするような実装内容です。
Webページを表示しシェアしていますが、それ以外でも同じような実装方法でシェア可能です。

シェア先は次の6個!(mixiは登記簿謄本が必要な為今回は対象外としました)

実装結果イメージ(「safari」と「URLをコピー」はおまけ)

f:id:Amarron:20140828232150p:plain

この記事では、「UIApplication sharedApplication」で実装しています。
iOS6移行はUIActivityViewControllerで実装するのが楽だと思うので、iOS6移行は他の記事を参考にする方がお勧めです!

前提条件

TWITTERFACEBOOKiOS6以上)

Xcodeのライブラリーに「Social.framework」を追加する必要があります。
(TARGETS->MyApp->Build Phases->Link Binary With Libraries)

Google+EverNote

Google+EverNoteは、SDKやサービスの登録が必要となります。
EverNoteはサービス登録が必要になるので、少し時間と手間がかかります)

簡単な概要は次の通りです。

Google+

名称 Google+ Platform
URL https://developers.google.com/+/
必要なライブラリ Security.framework,SystemConfiguration.framework
手順 1. APIs Console プロジェクトを作成する
2. SDK をダウンロードして、サンプル アプリを実行する
3. SDK のダウンロードとインストール
4. プログラム修正
参考URL スタートガイド(iOS)
Googleでログイン
インタラクティブな投稿の作成
備考 開発用と製品用でIDが異なる。使えないhtmlタグがある。

EverNote

名称 EverNote Developers
URL https://dev.evernote.com/intl/jp/
必要なライブラリ Security.framework
手順 1.サンドボックスのアカウント登録(アプリごとで共通のアカウントを利用する)
2. Evernote API キー取得(アプリごと別々に取得)
3. SDK のダウンロードとインストール
4.プログラム修正・サンドボックス上のEverNoteでテスト
5.サンドボックスからプロダクションに変更依頼(アクティベーション
6.プログラム修正(プロダクションに変更(ホストを「BootstrapServerBaseURLStringSandbox」→「BootstrapServerBaseURLStringUS」))
参考URL スタートガイド
サンドボックス
アクティベーション(プロダクションAPIキーの有効化)
よくある質問
備考 開発用と製品用でIDが異なる。使えないhtmlタグがある。
アクティベーションの申請時にはgoogleアカウントは使用できないので個人名・個人用メールアドレスで依頼
アクティベーションの承認待ち時間は約2〜3日

プログラム

流れ

  1. アクションシートの生成
  2. アクションシートの表示(シェアボタン押下時)
  3. アクションシートの選択した内容でシェア

サンプルコード

#import <Social/Social.h>
#import "EvernoteSession.h"
#import "EvernoteUserStore.h"
#import "EvernoteNoteStore.h"
#import "GTLPlusConstants.h"
#import "GPPShare.h"

@implementation MainController
    UIActionSheet *_actionsheet;
    UIWebView *_webView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // 1. アクションシートの生成
    _actionsheet = [[UIActionSheet alloc] initWithTitle:@"共有"
                                               delegate:self
                                      cancelButtonTitle:@"キャンセル"
                                 destructiveButtonTitle:nil
                                      otherButtonTitles:@"LINE", @"Twitter", @"Facebook", @"Google+", @"メール", @"Safari", @"Evernote", @"URLをコピー", nil];
    [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.co.jp"]]];

}

#pragma mark - IBAction
// 2. アクションシートの表示(シェアボタン押下時)
- (IBAction)shareAction:(id)sender
{
    if([[UIDevice currentDevice].model isEqualToString:@"iPad"]){
        [_actionsheet showFromBarButtonItem:sender animated:YES];
    } else {
        [_actionsheet showInView:self.view];
    }
}

#pragma mark UIActionSheetDelegate
// 3. アクションシートの選択した内容でシェア
-(void)actionSheet:(UIActionSheet*)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {
    NSURL *url = [NSURL URLWithString:[_webView stringByEvaluatingJavaScriptFromString:@"document.URL"]];
    NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"]; 

    if (0 == buttonIndex) { // LINE
        NSString *contentURL = [self _escapeURL:[url absoluteString]];
        NSString *contentTitleText = [self _escapeURL:title];
        NSURL *lineURL = [NSURL URLWithString:[NSString stringWithFormat: @"http://line.naver.jp/R/msg/text/%@?%@", contentURL, contentTitleText]];
        [[UIApplication sharedApplication] openURL:lineURL];
           
        
    } else if (1 == buttonIndex) { // TWITTER
        SLComposeViewController *postVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [postVC setInitialText:title];
        [postVC addURL:url];
        [self presentViewController:postVC animated:YES completion:nil];
        
    } else if (2 == buttonIndex) { // FACEBOOK
        SLComposeViewController *postVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
        [postVC setInitialText:title];
        [postVC addURL:url];
        [self presentViewController:postVC animated:YES completion:nil];

    } else if (3 == buttonIndex) { // Google+
        id<GPPShareBuilder> shareBuilder = [[GPPShare sharedInstance] shareDialog];
        [shareBuilder setPrefillText:title];
        [shareBuilder setURLToShare:url];
        [shareBuilder open];
        
    } else if (4 == buttonIndex) { // メール
        NSString *conSbjText = [self _escapeURL:title];
        NSString *conBodyText = [self _escapeURL:[url absoluteString]];
        NSString *mail = [NSString stringWithFormat:@"mailto:%@?Subject=%@&body=%@", @"", conSbjText, conBodyText];
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mail]];
        
    } else if (5 == buttonIndex) { // Safari
        [[UIApplication sharedApplication] openURL:url];
        
    } else if (6 == buttonIndex) { // EverNote
        EvernoteSession *session = [EvernoteSession sharedSession];
        [session authenticateWithViewController:self completionHandler:^(NSError *error) {
            if (!error && session.isAuthenticated){
                // We're authenticated!
                EvernoteUserStore *userStore = [EvernoteUserStore userStore];
                [userStore getUserWithSuccess:^(EDAMUser *user) {
                    // success
                    NSLog(@"Authenticated as %@", [user username]);
                    
                    EDAMNote *note = [[EDAMNote alloc] init];
                    
                    note.title = title;
                    
                    NSString *conDes = [_webView stringByEvaluatingJavaScriptFromString:
                                          @"function a(){l=document.getElementsByTagName('meta');for(i=0;i<=l.length;i++){if(l[i].name=='description') return l[i].content}};a();"];
                    NSString *conImg = [_webView stringByEvaluatingJavaScriptFromString:
                                        @"function a(){l=document.getElementsByTagName('meta');for(i=0;i<=l.length;i++){if(l[i].getAttribute('property')=='og:image') return l[i].content}};a();"];
                    
                    NSString *enNote = [NSString stringWithFormat:@"<p>%@</p><a href='%@'></a>%@<img src='%@' />",
                                        conDes,
                                        [self _escapeXML:[url absoluteString]],
                                        [self _escapeXML:[url absoluteString]],
                                        conImg];
                    note.content = [NSString stringWithFormat:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\"><en-note>%@</en-note>", enNote];
                    
                    EvernoteNoteStore *store = [[EvernoteNoteStore alloc] initWithSession:[EvernoteSession sharedSession]];
                    [store createNote:note success:^(EDAMNote *note) {
                        [[[UIAlertView alloc] initWithTitle:@"Evernoteに保存しました。" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
                    } failure:^(NSError *error) {
                        [[[UIAlertView alloc] initWithTitle:@"Evernoteの保存に失敗しました。" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
                    }];
                } failure:^(NSError *error) {
                    [[[UIAlertView alloc] initWithTitle:@"Evernoteの保存に失敗しました。" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];

                } ];
            }
        }];
    } else if (7 == buttonIndex) { // コピー
        [[UIPasteboard generalPasteboard] setValue:[url absoluteString] forPasteboardType:@"public.text"];
        [[[UIAlertView alloc] initWithTitle:@"URLをコピーしました。" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
}

#pragma mark - private methods
- (NSString *)_escapeURL:(NSString *) url
{
    return (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                         (__bridge CFStringRef)url,
                                                                         NULL,
                                                                         (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                         kCFStringEncodingUTF8 );

}
- (NSString *)_escapeXML:(NSString *) xml
{
    xml = [xml stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"\""withString:@"&quot;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"'"withString:@"&apos;"];
    xml = [xml stringByReplacingOccurrencesOfString:@">" withString:@"&gt;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"];
    return xml;
}

エンコードについて

Line、メール、EverNoteは、直接URLをシェア出来ません。
シェアするためには、htmlのエスケープが必要になります。
urlのエスケープ(line,mail)→16進表記(文字参照
xmlのエスケープ(EverNote)→実態参照

文字 実体参照 10進表記(文字参照 16進表記(文字参照
"&" &amp; &#38; &#x26;
""" &quot; &#34; &#x22;
"<" &lt; &#60; &#x3C;
">" &gt; &#62; &#x3E;
" " &nbsp; &#160; &#xA0;

App Store の iOSアプリ を 公開停止 する 方法 

AppStore の アプリ を 公開停止 する 方法 

概要

AppStoreに公開中のiOSアプリを取り下げる方法の説明です。
(申請(公開前のレビュー依頼)を取り下げたい場合、Developer Rejectで対応してください)
アプリの公開地域を全て解除することでAppStoreで公開されなくなります。

手順

  1. iTunes Connectへログイン
  2. Manage Your Appsを選択
  3. 公開停止したいアプリを選択
  4. 「Rights and Pricing」を押下
  5. Appの使用可能日・価格帯・地域の設定画面が表示されるので公開地域を解除します
    1. 「特定の地域」リンクを押下(公開中の地域が表示される)
    2. 「すべての選択を解除」ボタンを押下(全ての地域のチェックが外れる)
    3. 「Save」ボタン押下
  6. Statusが「Ready for Sale」から「Developer Removed From Sale」となり、App Infomationに「Delete App」ボタンが表示されます。(「Delete App」を選択しなくてもAppStoreからの公開は停止されました。)

f:id:Amarron:20140731170541p:plain

f:id:Amarron:20140731170550p:plain

f:id:Amarron:20140731170557p:plain

その他

  • 私の場合、公開停止の手続きをしてから30分くらいでAppStoreから公開されなくなりました。
  • AppStoreに一度も公開されなかったアプリは、公開地域を全解除しても「Delete App」が表示されません。(間違って作ってしまった場合、アプリを消したいけど消せなくて結構辛いです。。。)

Mac で よく使う キーボードショートカット まとめ

f:id:Amarron:20110722135432j:plain

MacOS X)で個人的によく使うキーボードショートカット(コマンド)を用途別にまとめてみました。

参考URL: OS X のキーボードショートカット

ターミナル

ターミナルでの操作

ショートカットキー 説明 詳細
control + A 行頭に移動 現在の行/段落の先頭に移動
control + E 行末に移動 現在の行/段落の末尾に移動
option + 右矢印キー 単語の末尾に移動 テキスト挿入ポイントを次の単語の末尾に移動
option + 左矢印キー 単語の先頭に移動 テキスト挿入ポイントを前の単語の先頭に移動
control + W 単語先頭まで削除 テキスト挿入ポイントから前の単語の先頭まで削除
control + D 1文字削除、ログアウト カーソル文字を1文字削除、ログアウト
control + K 右側全て削除 カーソルの右側の文字から行/段落の末尾までを削除
control + U 左側全て削除 カーソルの左側の文字から行/段落の先頭までを削除
control + L 再表示 画面をクリアしてカレント行を再表示する
tab 予測補完 コマンドやディレクトリ名を補完する
control + C 処理中断 実行中の処理を中断する
control + S 出力停止 画面への出力を中断する
control + Q 出力再開 画面への出力を再開する
control + Z 処理一時停止 実行中の処理を一時停止する

テキスト編集

テキストエディタで文章編集時の操作(viやemacsではない)

ショートカットキー 説明 詳細
command + A 全選択 前面に表示されている Finder ウインドウ (ウインドウが開かれていない場合はデスクトップ) のすべての項目を選択
command + C コピー 選択した項目/テキストをクリップボードにコピー
command + V ペースト クリップボードの内容をペースト
command + X 切り取り 選択した項目/テキストをクリップボードにコピーしテキストは削除
command + Z 動作を戻す 取り消す/やり直す
command + Y 動作を進める command + Zと反対の動作
command + S 保存 ドキュメントを保存
command + F 検索 検索ウインドウを開く
command + 上矢印キー 先頭に移動 テキスト挿入ポイントを現在のドキュメントの先頭に移動
command + 下矢印キー 末尾に移動 テキスト挿入ポイントを現在のドキュメントの末尾に移動
command + 左矢印キー 行頭に移動 テキスト挿入ポイントを現在のドキュメントの行頭に移動
command + 右矢印キー 行末に移動 テキスト挿入ポイントを現在のドキュメントの行末に移動
option + エリア選択 範囲指定エリア選択 optionを押しながらトラックパッドで範囲指定出来る(行間にまたがって空白を削除したい時に便利)

アプリケーション

常によく使う操作

ショートカットキー 説明 詳細
command + tab アプリ切替え 開いているアプリケーションのリスト (最近使った順番に表示されている) 内を順方向に移動
command + N 新規立上げ 最前面のアプリケーションで新規作成
command + W 閉じる 最前面のウインドウを閉じる
command + Q 終了 最前面のアプリケーションを終了
command + D 保存しないで閉じる 「開く」ダイアログと「保存」ダイアログで「デスクトップ」フォルダを選択・または、Mac OS X v10.6.8 以前で「保存しない」ボタンを含むダイアログから「保存しない」を選択
電源ボタン スリープ 電源が入ったら、タップしてスリープ解除/スリープ状態

Alfred

f:id:Amarron:20140705231226j:plain

Alfredとは、Macで利用できるランチャーアプリです。(App Storeでダウンロードできます)
もっとキーボードだけで操作したい人向けです。
個人的には、標準のショートカットにシャットダウンがないのでAlfredで「S」で検索しシャットダウンしています。(電源ボタン長押しで強制終了は出来るのですが、強制終了は気が引けるので)
その他にもアプリケーションを起動する時に良く利用します。一度使うと手放せなくなると思います。

iPhoneアプリ を iPadアプリ に 対応させる 方法

iPhoeアプリをiPadアプリとしてもAppStoreに配布する方法について書いてあります。 iPad対応するには、Xcodeで「ユニバーサルに変更」するのと「iPad用画像の設定」が必要となります。

環境

Xcode 5.1.1

流れ

  1. ユニバーサル化
  2. プログラム修正(修正が必要な場合のみ)
  3. 申請手続き

1. ユニバーサル化

DeviceをiPhoneからユニバーサルに変更する事でiPadに対応されます。

iPadのTAGETSを作ることで、TAGETS別にプログラムを変更出来るようになります。(「2. プログラム修正」で利用する場合があります)
ユニバーサルにすることによって、既存のiPhone用のxibがそのままiPadでも使えます。
また、iPadでアプリ起動時にiPhoneアプリとして起動するのではなく、iPad用アプリとして起動します。

  1. 「TARGETS->MyApp」を右クリックで「Depulicate」を選択
  2. 表示されたダイアログで「Duplicate and Transition to iPad」を選択(TARGETSにMyApp-iPadが追加されます)
  3. 1で選択した「「TARGETS->MyApp」の「General->Deployment Info->Devices」を「iPhone」から「Universal」に変更(この対応だけで(Depulicateで追加しなくても)iPadに対応します
  4. iPadで確認(実機の場合、右下に倍率を変更するボタンが表示されなければiPad用で開かれています)

f:id:Amarron:20140615124252p:plain

2. プログラム修正(修正が必要な場合のみ)

iPad用のアプリアイコン画像や起動時画像の追加、iPad用に細かいプログラム修正を行います。

  1. アプリアイコンの追加
    • 「TARGETS->MyApp->General->App Icons」でアプリアイコン画像(76x76,152x152)を設定
    • 「TARGETS->MyApp-iPad->General->App Icons」でアプリアイコン画像(72x72,144x144)を設定
  2. 起動画像の追加
    • 「TARGETS->MyApp->General->Launch Images」で起動画像画像(768X1024,1536X2048,1024X768,2048X1536)を設定
  3. プログラム修正(iPadで確認した時にレイアウト部分が崩れている場合は修正しましょう)
    • iOSアプリケーション プログラミングガイドによると「iPhone用とiPad用で、別々にビューコントローラのクラスを定義する」ことをお勧めしています。(別々に作った場合、Target Membershipにチェックを外すことでiPhoneiPadを切り替えることが出来ます)
    • 私は、新たにクラスを定義する程複雑なアプリではなかったので、同じクラスをif文で修正しました。(if([[UIDevice currentDevice].model isEqualToString:@"iPad"]){// iPadの調整})

f:id:Amarron:20140615124310p:plain

3. 申請手続き

  1. iTunes Connetで「Add Version」でバージョンアップ用アプリの追加手続きを行います。(iPhoneアプリと同じ手順)
  2. Xcodeでアーカイブ化しアプリをアップします。(iPhoneアプリと同じ手順)
  3. iTunes Connetでステータスが「Upload Received」に変更されると、iPad用のスクリーンショットを追加出来るようになるのでiPad用の画像(1536x2048 or 1536x2008)を設定しましょう。(iPadアプリで新しい手順)

※.1と2についてはわからない場合、App Store に iOSアプリ を 公開する方法の「3. 配布したいアプリの設定」を参照してください。

参考URL