会社概要

SwiftyJson


JSONデータのパースライブラリです。

CocoapodsのPodfileのメタ情報は

platform :ios, 'OSバージョン'
use_frameworks! target 'アプリ名' do
pod 'SwiftyJSON'
end

そしてターミナルから

$ pod install

Swiftファイル上での初期化は

import SwiftyJSON

let json = JSON(data: dataFromNetworking)

let json = JSON(jsonObject)

if let dataFromString = jsonString.data(using: .utf8, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
}


となり、JSONデータのアサイン例は

// doubleをアレイから取得
let name = json[0].double

// stringをマルチアレイから取得
let name = json[1]["list"][2]["name"].string

// JSON Dictionaryからstringを取得
let name = json["name"].stringValue

// Loopの場合
for (index,subJson):(String, JSON) in json {
//何か実行
}

//エラーの場合、アプトプット
let json = JSON(["name", "age"])
if let name = json[999].string {
//何か実行
} else {
print(json[999].error) // "Array[999] is out of bounds"
}

詳細は https://github.com/SwiftyJSON/SwiftyJSON

Alamorefire


HTTPデータ通信のライブラリです。

CocoapodsのPodfileのメタ情報は

platform :ios, 'OSバージョン'
use_frameworks! target 'アプリ名' do
pod 'Alamofire'
end


そしてターミナルから

$ pod install Swift

フィル上での初期化は

import Alamofire

Alamofireを使ってJSONファイルをダウンロードそしてパースする

Alamofire.request("URL文字列").responseJSON { response in

print(response.request) // URLリクエスト
print(response.response) // HTTP URLレスポンス
print(response.data) // サーバデータ
print(response.result) // ダウンロードの結果 Sucess/Failure if let JSON = response.result.value {
print("JSON: \(JSON)")
}

ファイルをダウンロードするには

Alamofire.download(URL文字列, to: destination)
.response { response in
let filepath: String = (response.destinationURL?.path)!
// テキストは、UTF8にエンコードする
self.TextView.text = try? String(contentsOfFile:(response.destinationURL?.path)!, encoding: String.Encoding.utf8)

// 既に同名のファイルが端末に存在すると(2回目のダウンロードから必ず発生)エラーが発生するので、一旦ファイルを消してからサーバからダウンロードする
if FileManager.default.fileExists(atPath: (response.destinationURL?.path)!) {
do{
try FileManager.default.removeItem(atPath: (response.destinationURL?.path)!) // 前に残ったファイルを削除
}catch{
print("Handle Exception")
}
}
}

詳細は https://github.com/Alamofire/Alamofire

iOS-Chart


iOS-Chartは人気グラフィックライブラリーなのですが、マニュアルがないのがネック。試行錯誤したなかの情報も含めて共有します。

CocoapodsのPodfileのメタ情報は

platform :ios, 'OSバージョン'
use_frameworks! target 'アプリ名' do
pod 'Charts'
end

そしてターミナルから

$ pod install

バーチャート作成のコードサンプルは

let y2Vals: [Double] = [ 900, 800, 1000, 500, 800, 900, 300, 400, 100, 1200, 500, 100]
xduration= ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

var entries2 = [BarChartDataEntry]()

for (i, v) in y2Vals.enumerated() {
let entry = BarChartDataEntry()
entry.x = Double(i)
entry.y = v
entry.data = xduration as AnyObject //x軸に文字列を指定する場合
entries2.append(entry)
}

let set2 = BarChartDataSet(values: entries2, label: "2016")
set2.setColor(UIColor.flatSkyBlue)

// チャートのスタイルの設定

BarChartView.xAxis.labelPosition = .bottom
BarChartView.drawValueAboveBarEnabled = true
BarChartView.xAxis.drawGridLinesEnabled = false
BarChartView.leftAxis.drawGridLinesEnabled = true
BarChartView.rightAxis.drawGridLinesEnabled = false
BarChartView.leftAxis.drawLabelsEnabled = true
BarChartView.rightAxis.drawLabelsEnabled = false

// チャートの間隔の調整

let data = BarChartData(dataSets: dataSets) let groupSpace = 0.25
let barSpace = 0.0
let barWidth = 0.3
let groupCount = self.xdurationtype.count
let startYear = 0

data.barWidth = barWidth;
BarChartView.xAxis.axisMinimum = Double(startYear)
let gg = data.groupWidth(groupSpace: groupSpace, barSpace: barSpace)
BarChartView.xAxis.axisMaximum = Double(startYear) + gg * Double(groupCount)
data.groupBars(fromX: Double(startYear), groupSpace: groupSpace, barSpace: barSpace)
BarChartView.notifyDataSetChanged()

// グラフ上のデータを非表示

let chartData = BarChartData()
chartData.addDataSet(set)
chartData.setDrawValues(false)

// オリエンテーション、デバイスタイプによりDescriptionの位置を変更

switch UIDevice.current.userInterfaceIdiom { // Detect Device Type and Orientation for Description location

case .phone:
if UIDevice.current.orientation.isLandscape {
self.MyChartView.chartDescription?.position = CGPoint(x: 適切な数値を設定, y: 0)
} else {
self.BarChartView.chartDescription?.position = CGPoint(x: 適切な数値を設定, y: 0)
}
case .pad:
if UIDevice.current.orientation.isLandscape {
self.MyChartView.chartDescription?.position = CGPoint(x: 適切な数値を設定, y: 0)
} else {
self.MyChartView.chartDescription?.position = CGPoint(x: 適切な数値を設定, y: 0)
}
default:
if UIDevice.current.orientation.isLandscape {
self.MyChartView.chartDescription?.position = CGPoint(x: 適切な数値を設定, y: 0)
} else {
self.MyChartView.chartDescription?.position = CGPoint(x: 適切な数値を設定, y: 0)
}
}

詳しくは https://github.com/danielgindi/Charts

Mapbox


Mapboxは、地図を利用するアプリに有用なSDK、クラウドサービスです。MapboxのウエブアプリMapbox Studioで地図を作成し、エリアのデータ(例えば人口が10万人のエリアの場合は赤にするなど)を計算たりして表示することができます。アプリ内のコードにても地図を作成できますが、アプリ内のコードの変更無しでMapboxのサーバーの地図データを編集することによりアプリ上での地図の変更ができるのは大きなメリットです。

SDKの設定方法(iOSの場合)

Info.plistにMGLMapboxAccessToken.を設定
app delegate methodに下記を追加

application:didFinishLaunchingWithOptions:, use -[MGLAccountManager setAccessToken:].

カスタム地図データの作成手順は:

1. GeoJSONデータのダウンロード
2. データセットの編集
3. タイルセットへのエクスポート
4. スタイルの作成

となります。

Mapboxを使うと

● カスタム地図
● ポリゴン描写
● マーカー描写
● カスタムマーカー
● アノテーションモデル
● データドリブン・サークル
● ポリゴンとパターン
● インタラクティブ・ポイント
● レイヤーのトグル
● ポイントからポイントのアニメーション
● ナビゲーション

などができます。

API情報は
https://www.mapbox.com/ios-sdk/api/3.6.4/

例えば、アノテーションのコードサンプルは、このような感じ。

let annotation = MGLPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736)
annotation.title = "Bobby's Coffee"
annotation.subtitle = "Coffeeshop"
mapView.addAnnotation(annotation)

ドキュメンテーションは
https://www.mapbox.com/ios-sdk/

価格は

5万回までの地図閲覧または5万アクティブユーザーまでは無料。これを越えると1000回毎または500アクティブユーザー毎に$0.5の従量課金制、詳細は下記まで。
https://www.mapbox.com/pricing/



Firebase


Firebaseは、グーグルが買収したベンチャー企業で、モバイルアプリの様々なプラットフォームを提供しています。各種サービスを提供していますが、今回の紹介は、無料サービスのCloud Messagingでモバイルアプリにメッセージを送ることができ、言語別、トピック別にメッセージを送付したり、送付するメセージのA/Bテストの機能もあります。

FireBaseの面白いところは、Googleが買収したので、Googleのユーザー属性に関するデータにアクセスして分析結果がダッシュボードに表示されます。iTunes Connectのアナリティックスでは表示されないユーザーの性別、年齢、国の長めのリスト、アクセスしたスクリーンクラス、興味分野などを知ることができます。

設定方法(iOS)

Xcodeを開いて、GoogleService-Info.plistをダウンロードリンクから追加します。

CocapodのPodfileの設定は

pod 'Firebase/Core'
pod 'Firebase/Messaging'

次にFirebaseのコンソールからAPNs authentication keyを取得し、これをiTunesConnectで使用

アプリでの初期化は

import Firebase

AppleDelegate.swift内でのRemote notificatioinの登録をします。

if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self

let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: {_, _ in })

} else {
let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()

FIRMessaging delegateを設定

Messaging.messaging().delegate = self

Registration Tokenを受け取るには

let token = Messaging.messaging().fcmToken

AppleDelegate.swift内でのToken 生成のモニタリング

func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) { print("Firebase registration token: \(fcmToken)")

// TODO: If necessary send token to application server. // Note: This callback is fired at each app startup and whenever a new token is generated.
}

APNSTokenのマッピング

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
Messaging.messaging().apnsToken = deviceToken
}

詳細は
https://firebase.google.com/docs/cloud-messaging/?authuser=0

SwiftyStoreKit


アプリ内課金のコーディングは結構大変な作業。開発者としてはアプリの開発に専念したいところです。そこで便利なのが、このSwiftyStoreKitライブラリ。自社のアプリのロジックに合わせて課金の機能を組み込んで行きます。ライブラリのバージョンは、まだ1.0.0になっていませんが、利用しているアプリの実績も多い。何よりアプリ課金の開発コスト下げ、簡単に自分がしたいアプリ課金のロジックにインテグレーションでき、更にアラートメッセージもしっかりと豊富なのが魅力です。

初期設定(iOS Cocoapad)

pod 'SwiftyStoreKit'

トランザクションの終了

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
  for purchase in purchases {
   if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored {
    if purchase.needsFinishTransaction {
     // Deliver content from server, then:
     SwiftyStoreKit.finishTransaction(purchase.transaction)
     }
    print("purchased: \(purchase)")
   }
  }
 }
 return true
}

プロダクト情報取得

SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
 if let product = result.retrievedProducts.first {
   let priceString = product.localizedPrice!
   print("Product: \(product.localizedDescription), price: \(priceString)")
}
else if let invalidProductId = result.invalidProductIDs.first {
  return alertWithTitle("Could not retrieve product info", message: "Invalid product identifier: \(invalidProductId)")
} else {
   print("Error: \(result.error)")
 }
}

プロダクト購入(自動課金)

SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: true) { result in
 switch result {
 case .success(let purchase):
   print("Purchase Success: \(purchase.productId)") case .error(let error):
 switch error.code {
  case .unknown: print("Unknown error. Please contact support")
  case .clientInvalid: print("Not allowed to make the payment")
  case .paymentCancelled: break
  case .paymentInvalid: print("The purchase identifier was invalid")
  case .paymentNotAllowed: print("The device is not allowed to make the payment")
  case .storeProductNotAvailable: print("The product is not available in the current storefront")
  case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
  case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
  case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
  }
 }
}

過去の購入の復元(自動課金)(アップル要求条件項目)

SwiftyStoreKit.restorePurchases(atomically: true) { results in
  if results.restoreFailedPurchases.count > 0 {
  print("Restore Failed: \(results.restoreFailedPurchases)")
  }
  else if results.restoredPurchases.count > 0 {
   print("Restore Success: \(results.restoredPurchases)")
  }
  else {
   print("Nothing to Restore")
  }
}

レ シートの確認

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
 SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: false) { result in
  switch result {
  case .success(let receipt):
   print("Verify receipt success: \(receipt)")
  case .error(let error):
   print("Verify receipt failed: \(error)")  
  }
}


購入の確認

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
  switch result {
  case .success(let receipt):
   // Verify the purchase of Consumable or NonConsumable
   let purchaseResult = SwiftyStoreKit.verifyPurchase(
     productId: "com.musevisions.SwiftyStoreKit.Purchase1",
     inReceipt: receipt)

   switch purchaseResult {
   case .purchased(let receiptItem):
     print("Product is purchased: \(receiptItem)")
   case .notPurchased:
     print("The user has never purchased this product")
   }
  case .error(let error): print("Receipt verification failed: \(error)")
  }
}

定期購読の確認

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
  switch result {
  case .success(let receipt):
   // Verify the purchase of a Subscription
   let purchaseResult = SwiftyStoreKit.verifySubscription(
    type: .autoRenewable, // or .nonRenewing (see below)
    productId: "com.musevisions.SwiftyStoreKit.Subscription",
    inReceipt: receipt)

   switch purchaseResult {
   case .purchased(let expiryDate, let receiptItems):
    print("Product is valid until \(expiryDate)")
   case .expired(let expiryDate, let receiptItems):
    print("Product is expired since \(expiryDate)")
   case .notPurchased:
    print("The user has never purchased this product")
   }

  case .error(let error):
   print("Receipt verification failed: \(error)")
  }
}


詳細は
https://github.com/bizz84/SwiftyStoreKit

W-8BEN-E 及び EINの取得について


アプリの販売準備にあたり、iTunesconnectで税金情報の登録が必要となります。アプリを米国内で販売するとApple社経由でも、日本の会社が米国内で事業してると見なされ、課税対象となります。その際、外国企業(米国から見ると日本企業)は、日米租税条約に基づき、米国側での30%の源泉徴収(InstructionのRequirements to Withholdの説明文に記載)の控除を受けられるため、iTunesconnect 税金情報サイト内の Form W-8BEN-Eに記入し、Apple社へオンラインで提出することを必ずオススメします。 Form W-8BEN-Eの記入の際、法人の場合は、EIN(雇用者識別番号)の記入が必要となりますので、EIN をIRS(米国税務局)から取得する必要があります。アップル社のサイトでW-8BENやEINの説明がありますが、内容が詳細でない部分もありましたので、IRS(米国税務局)からのEIN取得方法について共有します。

取得方法は、電話とファックスとフォームSS-4を米国へ郵送する3種がありますが、電話の場合は、その場でEIN番号を口頭で教えてもらえるので急ぎの場合はベストな選択肢です。電話番号 +1-267-941-109 はIRS(米国税務局)のサイトのApply by Telephone - International Applicantsのところに記載されています。名前、住所、電話番号を聞かれ、フォームSS-4に沿った内容の質問の受け答えをし、10分程で終了します。その後、2週間程でEINの番号の通知書類がIRSから日本へ発送されます。日本到着までは、そこから更に1〜2週間かかります。一般的にIRSは人手不足で書類処理に時間がかかる傾向があるようなので、上記以上にかかる可能性もあります。口頭でEIN取得後、即、iTunesconnectにEINを入力してもIRSの方でサーバに未だ反映さていないので、2週間ほど待ってからAppleのサイトで入力する必要があります。なお米国内の企業の場合は、IRSへ電話をかけてもオンラインのみでの受付なのでwww.irs.gov/irsへ行ってくださいと録音メッセージが流れます。しかしwww.irs.gov/irsで申請を始める場合、最初のステップ(1. Identity)のところで、日本企業にあてはまる法人形態がなく、海外の企業はオンラインの受付はしないので、(国番号 1)267-941-1099へ電話をしてくださいと下記のようにサイトに表示されます 。

Why is the Corporation requesting an EIN? Note: If you were incorporated outside of the United States or U.S. territories, you cannot apply for an EIN online. Please call us at 267-941-1099 (this is not a toll free number). Please exit the application by clicking “Exit” above.

なお、IRSからの質問の際、会社の業種の選択肢で、ITやソフトウェア開発はないので、IRSの方の指示で「製造業」でその製品が「ソフトウェア」という形で対応できます。 留意点ですが、SS-4の質問項目11番の事業開始年月日は、たとえ企業の設立が20年前でも、米国での課税対象となる事業(今回はアプリの販売開始)が来月からであれば、日付は来月とし、20年前にしてしまうと、米国の税務局が過去に遡って追徴課税があるかの調査対象になってしまう可能性があリます。SS-4の記入説明書には、外国企業(米国から見て)の場合は、(日本国内での)事業開始日ではなく米国内での事業を始めた日(今回のケースはApple社を通じてアプリの販売)または他のビジネスを買収した日を記入せよと記載されています。