20190811のiOSに関する記事は8件です。

Vision Frameworkを使ってリアルタイムでユーザーの顔をトラッキングしてみた

iOSアプリに顔認識を導入しようと思うと、昔ならOpenCVやTensorFlowでモデルをつくって…というようにいささかハードルが高く感じられたものですが、AppleさんがVisionフレームワークを用意してくれたおかけで、そのハードルは大きく下がりました。

今回、Appleのサンプルコードを使いながら、リアルタイムでユーザーの顔をトラッキングしてみました。

できるアプリ

vision_6.png
このように顔の中の特徴的な部分(ランドマーク)を勝手に検出し、Bounding Box(境界ボックス)で囲ってくれます!
境界ボックスは、顔が移動すると追跡してくれるので、境界ボックスの座標をもとにして、顔が画面のどこに寄っているかなどもとることができます。

サンプルコード

https://developer.apple.com/documentation/vision/tracking_the_user_s_face_in_real_time
このページのDownloadボタンからダウンロードして、実機でビルドすると顔認識アプリができます。

解説

サンプルコードの239行目あたりの

let faceDetectionRequest = VNDetectFaceRectanglesRequest(completionHandler: { (request, error) in

    if error != nil {
        print("FaceDetection error: \(String(describing: error)).")
    }

    guard let faceDetectionRequest = request as? VNDetectFaceRectanglesRequest,
        let results = faceDetectionRequest.results as? [VNFaceObservation] else {
            return
    }
    DispatchQueue.main.async {
        // Add the observations to the tracking list
        for observation in results {
            let faceTrackingRequest = VNTrackObjectRequest(detectedObjectObservation: observation)
            requests.append(faceTrackingRequest)
        }
        self.trackingRequests = requests
    }
})

で、検出された顔の解析が行われています。

ここの

for observation in results {
    let faceTrackingRequest = VNTrackObjectRequest(detectedObjectObservation: observation)
    requests.append(faceTrackingRequest)
}

のobservationから顔のランドマークやロール、ヨー角など様々な情報にアクセスできます。

たとえば、

for observation in results {
    let faceTrackingRequest = VNTrackObjectRequest(detectedObjectObservation: observation)
    requests.append(faceTrackingRequest)
    // 顔のロールの取得
    print(observation.roll)
}

のようなイメージです。

ついでに境界ボックスから顔が画面のどこに寄っているかもとってみましょう。

サンプルコードの554行目あたりの

guard let observation = trackingResults[0] as? VNDetectedObjectObservation else {
    return
}

で、トラッキングしたユーザーの顔の結果を取得しているので、このobservationを使って、境界ボックスの座標を取得します。

if observation.boundingBox.maxX >= 0.9 {
    print("顔が画面の右に寄っています")
}

上のように書くことで境界ボックスのX座標の画面からの比率を取得することができます。(画面の右端が1です)

今回は境界ボックスのX座標が0.9以上で画面の右に寄っていると出力するようにしてみました。

まとめ

Appleの機械学習で良いイメージはあまりなかったのですが、Visionフレームワークは予想以上に便利だなと思いました。

ただ、動画内でのまばたきや笑顔の検出はVisionだけでは厳しそうなので、Amazon Rekognition Videoなどを使う必要がありそうです。

とはいっても予想以上に面白かったので、これからもCore MLやCreate MLなどで遊んでみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】XMLParserについて

XMLParserについてまとめました。
Yahoo! RSS( https://news.yahoo.co.jp/pickup/rss.xml )を使用して、最新の記事の情報を取得します。

解析するXMLについて

Yahoo! RSSのXMLは以下のようになっています。

<rss xmlns:blogChannel="http://backend.userland.com/blogChannelModule" version="2.0">
   <channel>
      <title>Yahoo!ニュース・トピックス - 主要</title>
      <link>https://news.yahoo.co.jp/</link>
      <description>Yahoo! JAPANのニュース・トピックスで取り上げている最新の見出しを提供しています。</description>
      <language>ja</language>
      <pubDate>Sun, 11 Aug 2019 02:17:25 +0900</pubDate>
      <item>
         <title>記事のタイトル</title>
         <link>記事のURL(Link)</link>
         <pubDate>配信時間</pubDate>
         <enclosure length="133" url="https://s.yimg.jp/images/icon/photo.gif" type="image/gif">          </enclosure>
         <guid isPermaLink="false">記事のURL(PermaLink)</guid>
      </item>
      <item>・・・</item>
   </channel>
</rss>

今回は、記事のタイトルとURLの情報がほしいため、< title >と< link >の要素を取得します。

XMLの取得

ViewControllerにXMLParserDelegateプロトコルを実装させます。

class ViewController: UIViewController, XMLParserDelegate, UITableViewDataSource, UITableViewDelegate {

}

URLSessionを使用し、XMLをDataで取得します。
取得後にDataの中身を解析します。

let url: URL = URL(string:"https://news.yahoo.co.jp/pickup/rss.xml")!

let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
   let parser: XMLParser? = XMLParser(data: data!)
   parser!.delegate = self
   parser!.parse()
})
//タスク開始
task.resume()

XMLParserの解析処理

要素の取得時に呼び出される関数から、記事のタイトル等の値を取得します。

var check_title = [String]()
var news_title = [String]()
var link = [String]()
var enclosure = [String]()
var check_element = String()

//解析_開始時
func parserDidStartDocument(_ parser: XMLParser) {

}


//解析_要素の開始時
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
    if elementName == "enclosure" {
        enclosure.append(attributeDict["url"]!)
    }
    check_element = elementName
}

//解析_要素内の値取得
func parser(_ parser: XMLParser, foundCharacters string: String) {
    if string != "\n" {
        if check_element == "title" {
            //要素がtitleの場合値を取得する
            check_title.append(string)
        }

        if check_element == "link" {
            //要素がlinkの場合値を取得する
            link.append(string)
        }
    }
}

//解析_要素の終了時
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    if check_element == "title" {
        var title = check_title[0]
        for i in 1..<check_title.count {
            //titleの値が複数取得された場合一つにまとめる
            title = title + check_title[i]
        }
        check_title = [String]()
        news_title.append(title)
    }
}

//解析_終了時
func parserDidEndDocument(_ parser: XMLParser) {
![undefined]()

}

//解析_エラー発生時
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
    print("エラー:" + parseError.localizedDescription)
}

実行結果

XMLParserで取得した値から記事の一覧を表示しています。
タイトルを選択するとUIWebViewで記事の内容を表示します。

実行結果.gif

ソースコード

参考

https://developer.apple.com/documentation/foundation/xmlparserdelegate
https://mizumotok.hatenablog.jp/entry/2018/04/13/001002

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftでAPIを用いる流れ(ライブラリを使わないversion)

iOSアプリにおいて、 AlamofireやSwiftyJSONなどのライブラリを用いずにAPIを利用する流れについてまとめてみました。 

使用するもの

やること

TableViewにAPIから取得したデータを一覧で表示する。
サーバーにHTTPリクエストを送り、返ってきたHTTPリクエストを受け取ってTableViewに表示する。

手順

①Codableを使ってJSONを受け取る構造体(モデル)を作る。

QiitaStruct.swift
struct QiitaStruct: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var name: String
    }
}

②HTTPリクエストの生成

URLオブジェクトの生成

QiitaViewController.swift
let url = "https://qiita.com/api/v2/items"

URLRequestオブジェクトの生成

QiitaViewController.swift
let request = URLRequest(url: url)

③HTTPリクエストを投げ、返ってきたレスポンスを受け取って配列に格納する。

URLSessionクラスのインスタンスからリクエストを送り、その後のデータを受け取った後の処理について記述する。

QiitaViewController.swift
let qiitaArray:[QiitaStruct] = []
let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
    guard let jsonData = data else { return }

    do {
        let articles = try JSONDecoder().decode([QiitaStruct].self, from: jsonData)
        qiitaArray.append(articles)   
    } catch {
        print(error.localizedDescription)
    }
}
task.resume()

④格納した配列の値をTableViewに表示する。

QiitaViewController.swift
extension QiitaViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
        let article = articles[indexPath.row]
        cell.textLabel?.text = article.title
        cell.detailTextLabel?.text = article.user.name
        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return articles.count
    }

}

以上の手順により、以下のようにTableViewにデータを表示することができました。

Simulator Screen Shot - iPhone Xʀ - 2019-08-11 at 20.40.25.png

作成したQiitaAPIのサンプルプロジェクトは以下のGithubページからクローンできます。
サンプルプロジェクトでは、MVVM設計でコンポーネントを作って、少しだけ実装を抽象化しています。
https://github.com/YukiNagai1016/QiitaAPI

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterでネイティブのViewをそのまま表示できるPlatformViewを使う方法

FlutterのPlatformViewという ネイティブのViewをFlutterのWidgetのように表示できる というものです。

自分のプロジェクトでは画像の加工をよくやるので、結構使いそうです。
メモとして残します。

今回はiOSだけ紹介しますが、Androidも基本的には変わりません。
Androidでやる場合はこちら → https://medium.com/flutter-community/flutter-platformview-how-to-create-flutter-widgets-from-native-views-366e378115b6

info.plist を編集する

下記を info.plist を追加しましょう。

<key>io.flutter.embedded_views_preview</key>
<true/>

Flutter側を編集する

Flutter側はこれだけです。

 UiKitView( viewType: "ramdom_noise",),

iOS側

iOS側はAppDelegate.swiftを使います。

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
        ) -> Bool {


        let viewFactory = FluffViewFactory()
        registrar(forPlugin: "kitty").register(viewFactory, withId: "ramdom_noise")

        GeneratedPluginRegistrant.register(with: self)

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

public class FluffViewFactory: NSObject, FlutterPlatformViewFactory {
    public func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
        ) -> FlutterPlatformView {
        return FluffView(frame, viewId: viewId, args: args)
    }
}

public class FluffView : NSObject, FlutterPlatformView {
    let frame: CGRect
    let viewId: Int64

    init(_ frame: CGRect, viewId: Int64, args: Any?) {
        self.frame = frame
        self.viewId = viewId
    }


    public func view() -> UIView {
        return UISlider(frame: frame)
    }    
}

これで画面に Sliderが表示されます。

参考

https://medium.com/@phoomparin/how-to-use-native-uis-in-flutter-with-swift-platform-view-8b4dc7f833d8

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java / Swift]Java Interface と Swift Protocol の比較

この記事で書くこと

Java interface と Swift Protocol の比較

この記事を書いた理由

・ 基本情技術者試験対策
・ Java と Swift で似ている箇所の比較

コード例(Java interface)

main.java
public class Main {

    interface sampleInterface{
        String sampleFunction(String a, String b);
    }


    public static void main(String[] args) throws Exception {

    /* クラスに interface を実装する時は、implements と書く */    
    class A implements sampleInterface{
        public String sampleFunction(String a, String b){
            return "「" + a + "」と「" + b + "」はclassAに定義されたsampleFunctionの引数";
        }
    }

    class B implements sampleInterface{
        public String sampleFunction(String a, String b){
            return "「" + a + "」と「" + b + "」はclassBに定義されたsampleFunctionの引数";
        }
    }

    A a = new A();
    B b = new B();

    System.out.println(a.sampleFunction("c","d"));
    System.out.println(b.sampleFunction("e","f"));

    }
}

出力結果(Java)

「c」と「d」はclassAに定義されたsampleFunctionの引数
「e」と「f」はclassBに定義されたsampleFunctionの引数

コード例(Swift Protocol)

sample.swift
// プロトコル
protocol SampleProtocol{
    func sampleFunction(_ a:String, _ b:String) -> String
}

// class A
class A:SampleProtocol{
    func sampleFunction(_ a:String, _ b:String) -> String{
        return "「\(a)」と「\(b)」はclassAに定義されたsampleFunctionの引数"
    }
}


// class B
class B:SampleProtocol{
    func sampleFunction(_ a:String, _ b:String) -> String{
        return "「\(a)」と「\(b)」はclassBに定義されたsampleFunctionの引数"
    }
}


var a = A()
var b = B()

print(a.sampleFunction("c","d"))
print(b.sampleFunction("e","f"))

出力結果(swift)

「c」と「d」はclassAに定義されたsampleFunctionの引数
「e」と「f」はclassBに定義されたsampleFunctionの引数
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS, Swift】Metal躓きポイント

※この記事は随時追加更新していきます。

AppleのGPU shader言語であるMetalを使っていて躓いたところについて色々と覚え書きです。
この記事は (現時点で) 以下の構成です。

  • MTLTextureをdeep copyする方法
  • MTLTextureをCVPixelBufferに変換する方法
  • Metalで処理後の動画を保存する方法

MTLTextureをdeep copyする

MTLTextureオブジェクトはクラスインスタンスなので参照型です。CVPixelBufferなどの値型とは異なり複製には注意が必要です。

MTLTextureクラスには値渡しのメソッドはなく、したがってshallow copyを避けるためには自前で値渡しもしくは新規生成のコードを書くしかありません。

実際にはMTLTextureを別の画像オブジェクトに変換して、それを使って新たなMTLTextureを生成するのが良いと思います。
各種画像クラスを用いてMTLTextureクラスを初期化できますが、筆者はCGImageを使うのが最も楽であると思っています。

// MTLDeviceは別途初期化された状態を想定。
// device: MTLDevice

// MTLTextureからCIImage生成。画像の向きを調整。
let textureLoader = MTKTextureLoader(device: device)
let textureLoaderOptions = [
    MTKTextureLoader.Option.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue),
    MTKTextureLoader.Option.textureStorageMode: NSNumber(value: MTLStorageMode.`private`.rawValue)
]

// CIImageに変換して画像の向きを整える。
let ciImage = CIImage(mtlTexture: texture!, options: nil)!
    .transformed(by: CGAffineTransform(scaleX: 1, y: -1)
    .translatedBy(x: 0, y: CGFloat(alphaTexture!.height)))

// CIImage -> CGImage
let ciContext = CIContext()
guard let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) else {return}

// Create new MTLTexture
let newTex = try? textureLoader.newTexture(cgImage: cgImage, options: textureLoaderOptions)

MTLTexture -> CVPixelBuffer

CVPixelBufferLockBaseAddress(pixelBuffer, [])

let pixelBufferBytes = CVPixelBufferGetBaseAddress(pixelBuffer)!        
let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)

let region = MTLRegionMake2D(0, 0, texture.width, texture.height)

// 以下を実行すれば、pixelBufferに画像データが入る。
texture.getBytes(pixelBufferBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)

CVPixelBufferUnlockBaseAddress(pixelBuffer, [])

Metalで処理後に動画を保存する

AVAssetWriterを使います。
ここでは下記のようにVideoSaverクラスを定義します。

import AVFoundation
import Metal
import MetalKit
import Photos

class VideoSaver {
    var isRecording = false
    var recordingStartTime = TimeInterval(0)

    private var url: URL?

    private var assetWriter: AVAssetWriter
    private var assetWriterVideoInput: AVAssetWriterInput
    private var assetWriterPixelBufferInput: AVAssetWriterInputPixelBufferAdaptor

    // AVAssetWriterを初期化
    init?(outputURL url: URL, size: CGSize) {
        do {
            assetWriter = try AVAssetWriter(outputURL: url, fileType: AVFileType.mp4)
        } catch {
            return nil
        }
        self.url = url
        let outputSettings: [String: Any] = [ AVVideoCodecKey : AVVideoCodecType.h264, AVVideoWidthKey : size.width, AVVideoHeightKey : size.height ]

        assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: outputSettings)
        assetWriterVideoInput.expectsMediaDataInRealTime = true

        let sourcePixelBufferAttributes: [String: Any] = [kCVPixelBufferPixelFormatTypeKey as String : kCVPixelFormatType_32BGRA, kCVPixelBufferWidthKey as String : size.width, kCVPixelBufferHeightKey as String : size.height]

        assetWriterPixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterVideoInput, sourcePixelBufferAttributes: sourcePixelBufferAttributes)
        assetWriter.add(assetWriterVideoInput)
    }

    // UIButtonなどから呼ぶ録画開始メソッド
    func startRecording() {
        assetWriter.startWriting()
        assetWriter.startSession(atSourceTime: CMTime.zero)

        recordingStartTime = CACurrentMediaTime()
        isRecording = true
    }

    // UIButtonなどから呼ぶ録画終了メソッド
    func endRecording(_ completionHandler: @escaping () -> ()) {
        isRecording = false

        assetWriterVideoInput.markAsFinished()
        assetWriter.finishWriting(completionHandler: completionHandler)

        outputVideos()
    }


    // フレームをアプリローカルの保存先パスに書き込むメソッド。後述。
    func writeFrame(forTexture texture: MTLTexture) {
        /*
         後述
        */ 
    }


    // PHPhotoLibraryを用いて、アプリローカルの保存先からiOSのGalleryへファイルを移す。
    private func outputVideos() {
        let url = self.url

        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)
        }) { (isCompleted, error) in
            if isCompleted {
                do {
                    try FileManager.default.removeItem(atPath: url!.path)
                    print("ファイル移動成功 : \(url!.lastPathComponent)")
                }
                catch {
                    print("ファイルコピー成功、ファイル移動失敗 : \(url!.lastPathComponent)")
                }
            }
            else {
                print("ファイルコピー失敗 : \(url!.lastPathComponent)")
            }
        }
    }

作った.mp4動画はアプリのローカルディレクトリに一時保存するようにします。

録画開始・終了メソッド

録画開始・終了は以下のように呼べば良い。

    func takeVideo() {
        if !isRec {
            guard let url = videoFileLocation() else {return}
            let size = CGSize(width: cTexture!.width, height: cTexture!.height)
            self.videoSaver = VideoSaver(outputURL: url, size: size)
            videoSaver!.startRecording()

            isRec = true
        } else {
            isRec = false
            videoSaver!.endRecording {
                print("Save Finished")
            }
        }
    }

textureを取得してAVAssetWriterに渡す

textureをストリームで得るために以下の位置でcurrentDrawableからMTLTextureを取得、を取得するようにします。

/*
下記Metal描画パラメータの各種設定はすべて省略

let view = self.view as? MTKView
view.device = MTLCreateSystemDefaultDevice()
view.delegate = self

let renderDestination = view
let renderPassDescriptor = renderDestination.currentRenderPassDescriptor
let currentDrawable = renderDestination.currentDrawable

let device = view.device
let commandQueue = device.makeCommandQueue()
let commandBuffer = commandQueue.makeCommandBuffer()
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
*/

renderEncoder.endEncoding()

// .endEncoding() ~ .present() の間に、以下を挿入。
self.cTexture =  currentDrawable.texture
if self.isRec {
    commandBuffer.addCompletedHandler { commandBuffer in
        self.videoSaver!.writeFrame(forTexture: self.cTexture!)
    }
}

commandBuffer.present(currentDrawable)
commandBuffer.commit()

textureをアプリローカルに一時保存

最後に、以下がアプリローカルへのフレームデータ書き込みメソッドです。

    func writeFrame(forTexture texture: MTLTexture) {
        if !isRecording {return}

        while !assetWriterVideoInput.isReadyForMoreMediaData {}

        // MTLTexure -> CVPixelBuffer -> AVAssetWriter's pixelBufferPool
        guard let pixelBufferPool = assetWriterPixelBufferInput.pixelBufferPool else {return}
        var maybePixelBuffer: CVPixelBuffer? = nil
        let status  = CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &maybePixelBuffer)
        if status != kCVReturnSuccess {return}

        guard let pixelBuffer = maybePixelBuffer else { return }
        CVPixelBufferLockBaseAddress(pixelBuffer, [])
        let pixelBufferBytes = CVPixelBufferGetBaseAddress(pixelBuffer)!
        let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        let region = MTLRegionMake2D(0, 0, texture.width, texture.height)
        texture.getBytes(pixelBufferBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)

        // AVAssetWriterへCVPixelBufferを追加。
        let frameTime = CACurrentMediaTime() - recordingStartTime
        let presentationTime = CMTimeMakeWithSeconds(frameTime, preferredTimescale: 240)
        assetWriterPixelBufferInput.append(pixelBuffer, withPresentationTime: presentationTime)

        CVPixelBufferUnlockBaseAddress(pixelBuffer, [])
    }

終わりに

ご参考になれば幸いです!
改善方法やご意見などあれば、どしどしコメント下さい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flutterでfirebase_authを追加したらRunning Xcode build...から進まない現象の対処法

pubspec.yamlに

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  rxdart: ^0.22.1+1
  firebase_auth: ^0.14.0+

と「firebase_auth」を追記すると、永遠に「Running Xcode build...」で終わらない。
スクリーンショット 2019-08-11 12.00.47.png

対処法

Firebase flutter-setupを確認すると、ios/RunnerにGoogleService-Info.plistを入れろと書いてあるけど、直接フォルダに入れても同じ現象になるので注意。
xcode上からGoogleService-Info.plistを追加してあげないと認識できないみたいで、この手順を実施すれば正常にビルドできるようになる。
スクリーンショット 2019-08-11 12.10.44.png

おわりに

わかるかこんなの!2時間ハマったわ。だからネイティブは(ry

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HealthKitを使ってアプリがクラッシュするケース

HealthKitを使ってrequestAuthorization()の直後にアプリがクラッシュする場合、plistに追加した「使用する理由」が短すぎるのが原因かも。

確認した環境

iOS: 12.3
Xcode: 10.2.1

遭遇した現象

HealthKitを使うための手続き
・HealthKitのCapabilityをONにする
・plistファイルにNSHealthShareUsageDescriptionキーを追加して理由を記載
・requestAuthorization()でHealthKitへのアクセス許可を求める
と、アプリケーションがクラッシュした。

上記だけの動作をさせてもクラッシュする&ステップ実行しても当たりがつかず、途方にくれる。。。

対処方法

plistファイルに追加したNSHealthShareUsageDescriptionキーの値を、
"hoge"から"hoge hhoge hoge hoge hoge hoge hoge hoge hoge hoge hoge hoge "と
とりあえず長くしてみるとクラッシュしなくなる。。。

とりあえず動かしてみようとして適当な文字列を入れてハマりました。
「文字列が短すぎるとクラッシュする」ということを聞いたのでやってみると解決。
エラー(原因)を表示してほしい!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む