SwiftでアプリからTwitterにGif投稿

アプリで撮影した動画をgifに変換し、twitterに投稿してみた。

TwitterGifをサポートしてるけど、既存のSLComposeViewControllerや、UIActivityViewControllergifをサポートしていないので少し面倒でした。

動画をgifに変換

Regiftというライブラリを使ったら簡単にできた

github.com

twitterに投稿

まずはActionSheetで全アカウントを表示し、選択されたaccountを返します

import Accounts

func selectAccount() -> AnyObject {
            let account = ACAccountStore()
            let accountType = account.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierTwitter)
            account.requestAccessToAccountsWithType(accountType, options: nil, completion: { (success: Bool, error: NSError!) -> Void in
                if success {
                    guard let accounts = account.accountsWithAccountType(accountType) else { return }
                    if !accounts.isEmpty {
                        let actionSheet = UIAlertController(title: "Choose Account", message: "", preferredStyle: .ActionSheet)
                        for account in accounts {
                            guard let name = account.username else { return }
                            let action = UIAlertAction(title: name, style: .Default, handler: { (action: UIAlertAction) in
                                return account
                            })
                            actionSheet.addAction(action)
                        }
                        self.presentViewController(actionSheet, animated: true, completion: nil)
                    }
                }
            })
        }
    }

選択されたアカウント、メッセージ、gifのNSDataでポスト。 メッセージはDictionaryで["status", ツイート内容]

func post(account: ACAccount, message: [String: String!], data: NSData) {
        let url = NSURL(string: "https://api.twitter.com/1.1/statuses/update_with_media.json")
        let postRequest = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: .POST, URL: url, parameters: message)
        postRequest.account = account
        postRequest.addMultipartData(data, withName: "media", type: "image/gif", filename: "image.gif")
        postRequest.performRequestWithHandler({ (responseData: NSData!, urlResponse: NSHTTPURLResponse!, error: NSError!) -> Void in
            print(urlResponse)
        })
    }

SwiftでAWS S3へデータをアップロード

AWS SDK for iOSを使ってアプリからAWS S3に動画をアップロードする処理をやってみた。 ローカルのデータをアップロードして、保存先のURLを取得します。

準備

  • プロジェクトにAWS SDKをインストール

pod 'AWSS3'

  • ユーザー認証の仕組みとかを実装できる Amazon CognitoというAWSサービスを登録する必要もあります。

  • S3の設定 Bucketを作成します。 permissionはとりあえず、EveryOne が UploadとRead できるようにしておきます。

これで準備完了。

実装

application:didFinishLaunchingWithOptionsでCognitoを使ってSDKのセットアップ

let credentialsProvider = AWSCognitoCredentialsProvider(regionType: RegionType, identityPoolId: IdentityPoolId)
let configuration = AWSServiceConfiguration(region: DefaultServiceRegionType, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration

実際にアップロードするところは

internal func upload() -> NSURL {
        let uploadRequest = AWSS3TransferManagerUploadRequest()
        uploadRequest.body = origin
        uploadRequest.key = remote
        uploadRequest.bucket = BucketName
        let transferManager = AWSS3TransferManager.defaultS3TransferManager()
        transferManager.upload(uploadRequest).continueWithBlock { task -> AnyObject! in
            if task.result != nil {
                if let bucket = uploadRequest.bucket, key = uploadRequest.key {
                    let url = NSURL(string: "http://s3.amazonaws.com/\(bucket)/\(key)")
                    return url
                }
            }
        }
    }

originにアップロードするデータのfilepath、remoteにS3に保存するデータの名前を入れます。 アップロードが成功すると保存先のURLが返ってきます。

使いやすいように、SwiftTaskを使ってこんな感じのクラスを作ってみた

import AWSS3
import SwiftTask

class S3Uploader {

    typealias UploadTask = Task<Void, NSURL, NSError>

    private var origin: NSURL?
    private var remote: String?

    internal init(origin: NSURL, remote: String) {
        self.origin = origin
        self.remote = remote
    }

    internal func upload() -> UploadTask {
        return UploadTask { progress, fulfill, reject, configure in
            guard let origin = self.origin, remote = self.remote else { return }
            let uploadRequest = AWSS3TransferManagerUploadRequest()
            uploadRequest.body = origin
            uploadRequest.key = remote
            uploadRequest.bucket = AppConfig.AWSS3.BucketName
            let transferManager = AWSS3TransferManager.defaultS3TransferManager()
            transferManager.upload(uploadRequest).continueWithBlock { task -> AnyObject! in
                if let error = task.error {
                    reject(error)
                }
                if task.result != nil {
                    if let bucket = uploadRequest.bucket, key = uploadRequest.key {
                        if let url = NSURL(string: "http://s3.amazonaws.com/\(bucket)/\(key)") {
                            fulfill(url)
                        }
                    }
                }
                return nil
            }
        }
    }
}

けっこう頻繁に更新されている公式のサンプルもあって、参考になった。(日本人の方がつくってるっぽい)

github.com

SwiftでLINEの桜が降るエフェクトを作ってみた

これです。

ほんとはLINEに実装される前にCocoaPodsになにか公開しようと思って作ったものなんだけど...

CoreAnimationのパーティクルシステム、CAEmitterLayerCAEmmiterCellを使ってけっこう簡単にできました。

github.com

f:id:aminaura:20160504173612p:plain

桜の他にも、タンポポとPlumも選べるようにした

SwiftでSnapChatっぽいUI

SwiftでSnapChatぽいUI、左右のスワイプでViewControllerが切り替わるやつをやってみた。

f:id:aminaura:20160503073406j:plain

まずは大元となるContainerViewControllerを作って(Containerという名前が正しいかはわからないけど)、その上に敷いたScrollViewにVCたちをaddChildViewControllerしていく感じ。

class ContainerViewController: UIViewController {

    private var offset = 0
    private var viewControllers = [UIViewController]()

    internal init(controllers: [UIViewController], offset: Int) {
        super.init(nibName: nil, bundle: nil)
        controllers.forEach { viewControllers.append($0) }
        self.offset = offset
    }

    private func setupScrollView() {
        let viewBounds = self.view.bounds
        let viewWidth = viewBounds.width

        let scrollView = UIScrollView()
        scrollView.delegate = self
        scrollView.pagingEnabled = true
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.bounces = false
        scrollView.frame = CGRect(x: viewBounds.origin.x, y: viewBounds.origin.y, width: viewWidth, height: CGRectGetHeight(viewBounds))

        self.view.addSubview(scrollView)
        scrollView.contentSize = CGSize(width: CGFloat(viewControllers.count) * viewWidth, height: CGRectGetHeight(viewBounds))

        for (index, vc) in viewControllers.enumerate() {
            vc.view.frame = CGRect(x: CGFloat(index)*viewWidth, y: 0, width: viewWidth, height: CGRectGetHeight(viewBounds))
            self.addChildViewController(vc)
            scrollView.addSubview(vc.view)
            vc.didMoveToParentViewController(self)
            scrollView.sendSubviewToBack(vc.view)
        }
        scrollView.contentOffset.x = viewControllers[offset].view.frame.origin.x
    }
}

ContainerViewControllerの初期化はアプリ起動時にこんな感じで行ってる。

internal func setup(application: UIApplication) {
        let cameraSB = UIStoryboard(name: "Camera", bundle: nil)
        let feedSB = UIStoryboard(name: "Feed", bundle: nil)
        let profileSB = UIStoryboard(name: "Profile", bundle: nil)

        let camera = cameraSB.instantiateViewControllerWithIdentifier("camera")
        let feed = feedSB.instantiateViewControllerWithIdentifier("feed")
        let profile = profileSB.instantiateViewControllerWithIdentifier("profile")
        let controllers = [feed, camera, profile]

        let container = ContainerViewController(controllers: controllers, offset: 1)
        self.window?.rootViewController = container
        self.window?.makeKeyAndVisible()
    }

(実際のコードから削っているのでそのままだと動かないかもしれないです)

Swiftで動画編集

swiftで動画(AVAsset)を逆再生や、早送りにして書き出すのをやってみた。

方法としては、

  1. AVAsset[CGImage]に変換する
  2. それを並び替えたりして、CAKeyframeAnimationを仕込んだCALayerに変換する
  3. addSublayerでプレビューする
  4. MOVとして書き出す

まずは、動画を画像配列に変換

extension AVAsset {

    func frames() -> [CGImage] {
        var images = [CGImage]()
        guard let track = tracksWithMediaType(AVMediaTypeVideo).first else { return [CGImage]() }

        var reader: AVAssetReader?
        do {
            reader = try AVAssetReader(asset: self)
        } catch let error as NSError {
            print(error)
        }

        let options = [
            "\(kCVPixelBufferPixelFormatTypeKey)": Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
        ]
        let output = AVAssetReaderTrackOutput(track: track, outputSettings: options)
        reader?.addOutput(output)
        reader?.startReading()

        while let sample = output.copyNextSampleBuffer() {
            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sample) else { return [CGImage]() }
            let ciImage = CIImage(CVPixelBuffer: pixelBuffer)
            let context = CIContext(options: [kCIContextUseSoftwareRenderer: true])
            let cgImage = context.createCGImage(ciImage, fromRect: ciImage.extent)
            images.append(cgImage)
        }

        return images
    }
}

次にその画像配列を並び替えたりして、アニメーションレイヤーを作成

任意の画像とdurationのセットで渡します

struct KeyFrame {
    internal let image: CGImage
    internal let duration: Double
}
extension CALayer {

    class func animation(keyFrames: [KeyFrame]) -> CALayer {
        let total = keyFrames.map({ $0.duration }).reduce(0, combine: +)
        let count = keyFrames.count
        var times = [Double]()
        var currentTime = 0.0
        for i in 0..<count {
            times.append(currentTime/total)
            currentTime += keyFrames[i].duration
        }
        let layer = CALayer()
        let animation = CAKeyframeAnimation(keyPath: "contents")
        animation.keyTimes = times
        animation.values = keyFrames.map { $0.image }
        animation.duration = total
        animation.repeatCount = .infinity
        animation.beginTime = AVCoreAnimationBeginTimeAtZero
        animation.removedOnCompletion = false
        animation.calculationMode = kCAAnimationDiscrete
        layer.addAnimation(animation, forKey: "contents")
        return layer
    }
}

これであとは、

let animationLayer = layer.animation(keyFrames)
            layer.frame = view.frame
            previewView.layer.addSublayer(layer)

みたいな感じでプレビユーできた!

最後書き出しはAVAssetExportSessionで簡単!