20200401のSwiftに関する記事は4件です。

【iOS】Dropbox iOS13対応

はじめに

・iOS13用にDropboxを対応したところ、ハマったので備忘録として記載。

環境

xcode11.0
swift 5.0.1
iOS13

これまで

・チュートリアル通りに進むと

AppDelegate.swift
  func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
      if let authResult = DropboxClientsManager.handleRedirectURL(url) {
          switch authResult {
          case .success:
              print("Success! User is logged into Dropbox.")
          case .cancel:
              print("Authorization flow was manually canceled by user!")
          case .error(_, let description):
              print("Error: \(description)")
          }
      }
      return true
  }

と記載していた。
iOS13ではSceneDelegate.swiftに移譲された為、変更が必要になる。

これから

・SceneDelegate.swiftへ以下を記載。

SceneDelegate.swift
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
    {

      // コールバックで来たURLの取得
      guard let url = URLContexts.first?.url else {
          return
      }

      if let authResult = DropboxClientsManager.handleRedirectURL(url) {
          switch authResult {
          case .success:
              print("Success! User is logged into Dropbox.")
          case .cancel:
              print("Authorization flow was manually canceled by user!")
          case .error(_, let description):
              print("Error: \(description)")
          }
      }
      return
  }

まとめ

・iOS13とそれ以前のiOSを対応させるように作成した為、AppDelegateが原因であることに気づくのに時間がかかった。
大型アップデート後の情報収集は怠ってはならない教訓になりました。

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

Windows10のUbuntu18.04でSwift 5.2.1とVapor 4.0.0へアップデート

環境をアップデートします。

以前の環境はノートPCと共にお亡くなりになりましたが、再度同環境を構築してVaporの動作を確認していました。しかしながらテンプレートがどうも上手く動かないようで、どうせ調べるなら最新がいいなぁと思いVapor4系に更新することにしました。

Swift 5.2.1

Vapor4系はSwift5.2以上が必要なので、まずSwiftを更新します。
タイムリーなことに、2020年3月30日に5.2.1がリリースされていました。

Ubuntu 18.04用をダウンロードして展開します。

5.1.5の時と同じように/usr/local下へコピーしてパスを通します。

.bashrcを編集

#export PATH=/usr/local/swift-5.1.5-RELEASE-ubuntu18.04/usr/bin:$PATH
export PATH=/usr/local/swift-5.2.1-RELEASE-ubuntu18.04/usr/bin:$PATH

ダメだった時に備えて、Swift5.1.5も残しておきます。

source .bashrc
swift --version

swift5.2.1.PNG

Swift5.2.1が使えるようになりました。

Vapor4

Vapor4.0のヘルプを見ながらVapor Toolboxをインストールします。

githubから取ってきて、ビルドします。

git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout は最新を使うのでやらない
swift build -c release --disable-sandbox

エラー

Fetching https://github.com/tanner0101/mustache.git
Fetching https://github.com/apple/swift-log.git
Fetching https://github.com/vapor/console-kit.git
Fetching https://github.com/apple/swift-nio.git
Fetching https://github.com/jpsim/Yams.git
Cloning https://github.com/jpsim/Yams.git
Resolving https://github.com/jpsim/Yams.git at 2.0.0
Cloning https://github.com/vapor/console-kit.git
Resolving https://github.com/vapor/console-kit.git at 4.0.0-rc.1
Cloning https://github.com/apple/swift-nio.git
Resolving https://github.com/apple/swift-nio.git at 2.15.0
Cloning https://github.com/apple/swift-log.git
Resolving https://github.com/apple/swift-log.git at 1.2.0
Cloning https://github.com/tanner0101/mustache.git
Resolving https://github.com/tanner0101/mustache.git at 0.1.0
error: missing LinuxMain.swift file in the Tests directory

空でもいいという噂もありましたが、LinuxMain.swift をTests下に以下な感じで作ってみます。

import XCTest
@testable import MonoGeneratorTests

XCTMain([
     testCase(GeneratorTests.allTests),
])

もう一回ビルドすると、なにやらWarningが出てますが、とりあえずvaporは出来ています。

/home/arimitsu/toolbox/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:32: warning: initialization of 'UnsafeMutablePointer<yaml_version_directive_t>' (aka 'UnsafeMutablePointer<yaml_version_directive_s>') results in a dangling pointer
            versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/arimitsu/toolbox/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:53: note: implicit argument conversion from 'yaml_version_directive_t' (aka 'yaml_version_directive_s') to 'UnsafeMutablePointer<yaml_version_directive_t>' (aka 'UnsafeMutablePointer<yaml_version_directive_s>') produces a pointer valid only for the duration of the call to 'init(_:)'
            versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                                    ^~~~~~~~~~~~~~~~~~~~~~
/home/arimitsu/toolbox/.build/checkouts/Yams/Sources/Yams/Emitter.swift:338:53: note: use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope
            versionDirective = UnsafeMutablePointer(&versionDirectiveValue)
                                                    ^
[22/22] Linking vapor

./.build/release下に出力された vapor を/usr/local/binの下へコピーしてヘルプを表示して確認する。

vapor --help

vapor-help.PNG
バージョンの確認はできなくなったらしい。

vapor --version これは無し

プロジェクト作成

新規にプロジェクトを作ってみます。

vapor-beta new hello -n

ん?
vapor-betaは無いだろうなぁ
-nはいらないかな

Vapor4.0.0なのでhello「4」の以下でいきます。

vapor new hello4

Fluentはとりあえず使いませんので「n」で。

Cloning template...
name: hello4
Would you like to use Fluent?
y/n> n
fluent: No
Generating project files
+ Package.swift
+ main.swift
+ configure.swift
+ routes.swift
+ .gitkeep
+ AppTests.swift
+ Dockerfile
+ docker-compose.yml
+ .gitignore
+ .dockerignore
Creating git repository
Adding first commit

ここでGitのエラーが出るかもしれません。その前にGitのユーザー名とメールアドレスを登録しておきましょう。

git config --global user.name [名前]
git config --global user.email [メールアドレス]

hello4.PNG
hello4へ移動してbuildします。

cd hello4
vapor build

core dumped !
coredump.PNG
意味不明ですね。

ネットで調べたところ、同じようにコアダンプしている人が質問してました。

  • 質問者 「Vaporでビルドするとコアダンプするんだけど、どうしたらいい?」
  • 回答者 「Swift試してみた?」
  • 質問者 「動いた!」

以上終わり?swiftでいいのかね?

swift build

hello4build.PNG
またか・・・
上でつくったLinuxMain.swift をTests下にコピーしてやります。

swift build

linkingrun.PNG

できた!

swift run

swiftrun.PNG

ブラウザからも動作を確認できました。

ブラウザ.PNG

途中でいろいろやったこと

ネットで調べてやってみましたがが、とりあえず必要なかったです。

Package.swift

プラットフォーム指定がmacOSだからダメなんじゃないの?でコメントアウト
platform.PNG
dependenciesで指定しているパッケージのfromが最新のものを固定で指定してみる
package.PNG

toolbox

toolboxのPackage.swiftでも、同様にやってみる。

toolboxのビルドで-Xswiftcをつけてみる、-g(何これ?)をつけてみる

最後に

ビルドのWarningが気になりますがerrorではないので致命的ではないし、とりあえず動くようにはなりましたので、テンプレートの確認に戻ります。
動くのかな?

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

独自プロパティリスト(plist)を利用する

iOSアプリで独自plist(プロパティリスト)を作って使いたい時のメモ。
※よくある、APIのURLとかその他アプリに特化した設定などに使用
※他の方がわかりやすく書いてくれているのもあるのでメモ程度

plistの作成

まずはお目当てのplistを作成します。

  • File→New→File(cmd+N)で新規作成画面を表示
  • Property Listを選択、名前(今回はApp.plist)を決めて作成

作成できたら、適当に値を設定しましょう。
今回はよくあるURLを記載します

作ったplistの読み込み

先程作ったApp.plistを読み込むコードが以下になります。

var property: Dictionary<String, Any> = [:]
// App.plistのパス取得
let path = Bundle.main.path(forResource: "App", ofType: "plist")
// App.plistをDictionary形式で読み込み
let configurations = NSDictionary(contentsOfFile: path!)
if let datasourceDictionary: [String : Any]  = configurations as? [String : Any] {
    property = configurations
}
// キーを指定して、値の取得
let url = property["url"] as! String
print(url)

データ取得部分も含めてクラス化

読み込み、データ取得を毎度書くのは無駄よってことで
クラス化します。

App.swift
class App {
    var property: Dictionary<String, Any> = [:]
    init() {
        // App.plistのパス取得
        let path = Bundle.main.path(forResource: "App", ofType: "plist")
        // App.plistをDictionary形式で読み込み
        let configurations = NSDictionary(contentsOfFile: path!)
        if let datasourceDictionary: [String : Any]  = configurations as? [String : Any] {
            property = datasourceDictionary
        }
    }

    /// 指定されたキーの値を取得する
    /// - Parameter key: plistのキー
    func getString(_ key: String) -> String? {
        guard let value: String = property[key] as? String else {
            return nil
        }
        return value
    }
}

さて、これで利用できるようになりました。
では実際に呼び出してみましょう。

var app = App()
print(app.getStirng("url"))

Appクラスをシングルトンにしてみる

シングルトンにすることで、読み込みを一回だけにしてあとはインスタンス再利用する形にします。
以下コードを追加

static let shared: App = {
    let instance = App()
    return instance
}()

全体

App.swift
class App {
    var property: Dictionary<String, Any> = [:]
    static let shared: App = {
        let instance = App()
        return instance
    }()
    private init() {
        // App.plist取得
        let path = Bundle.main.path(forResource: "App", ofType: "plist")
        let configurations = NSDictionary(contentsOfFile: path!)
        if let datasourceDictionary: [String : Any]  = configurations as? [String : Any] {
            property = datasourceDictionary
        }
    }

    /// 指定されたキーの値を取得する
    /// - Parameter key: plistのキー
    func getString(_ key: String) -> String? {
        guard let value: String = property[key] as? String else {
            return nil
        }
        return value
    }
}

さて、これで利用方法を変更します。

var app = App.shared
print(app.getStirng("url"))

これでアプリ独自プロパティも怖くない!!
次回は、これを環境別に設定できるようにしたいと思います。

参考

Swift で簡単なシングルトンの実装方法
Swiftでplistを読み込む

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

Transformを使用して簡単に視差効果を表現する

今回はアプリの初回チュートリアルなどでスクロールをカッコよく見せる演出として使われるパララックス(視差効果)を表現する実装をご紹介します。
今回のものはサンプルなので見た目はイマイチですが、参考程度にどうぞ。

完成イメージ

白い正方形のViewには視差効果を与えておらず、1ページ目の青いLabel、2ページ目の黒いLabelに視差効果を与えています。
青のLabelと白いViewのスクロール量が少しずれていることがわかると思います。

実装概要

  • UIScrollViewでページングをする横スクロールを作成します
  • UIScrollView内にはUIStackViewを配置し、任意のページ分のコンテンツを追加してスクロールできるようにします。
  • 各ページのUIViewのtransform, alphaにアクセスして、スクロール量に応じて値を変化させます。

View階層

実装

今回はUI実装の簡略化のためにSnapKitというライブラリを使用させていただきます。

import UIKit
import SnapKit

final class ParallaxViewController: UIViewController {
    let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.isPagingEnabled = true
        return scroll
    }()

    let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.alignment = .fill
        stack.backgroundColor = .purple
        return stack
    }()

    let containers: [LabelContainer] = [
        LabelContainer(labelColor: .blue),
        LabelContainer(labelColor: .black)
    ]

    private var contentOffsetObservation: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white

        configureScrollView()

        contentOffsetObservation = scrollView.observe(\.contentOffset) { [weak self] scrollView, _ in
            guard let me = self else { return }

            me.containers.enumerated().forEach { index, container in
                let distanceContainerToLabel = container.frame.origin.x - scrollView.contentOffset.x
                // スクロール量に応じてLabelの表示位置をずらす
                container.label.transform = .init(
                    translationX: distanceContainerToLabel * -0.3,
                    y: .zero
                )

                let limit = me.scrollView.bounds.width
                // スクロール量に応じてLabelの透明度を変化させる
                container.alpha = {
                    switch distanceContainerToLabel {
                    case -limit..<0, 0..<limit:
                        return 1 - abs(distanceContainerToLabel) / limit
                    default:
                        return .zero
                    }
                }()
            }
        }
    }

    private func configureScrollView() {
        view.addSubview(scrollView)
        scrollView.snp.makeConstraints {
            $0.top.left.right.equalToSuperview()
            $0.height.equalTo(300)
        }

        scrollView.addSubview(stackView)
        stackView.snp.makeConstraints {
            $0.edges.height.equalToSuperview()
        }

        containers.forEach { container in
            stackView.addArrangedSubview(container)
            container.snp.makeConstraints {
                $0.width.equalTo(view.snp.width)
            }
        }

        let box = UIView()
        box.backgroundColor = .white
        scrollView.addSubview(box)
        box.snp.makeConstraints {
            $0.width.height.equalTo(200)
            $0.center.equalToSuperview()
        }
    }
}

final class LabelContainer: UIView {
    let label: UILabel

    init(labelColor: UIColor) {
        label = makeLabel(color: labelColor)
        super.init(frame: .zero)

        addSubview(label)
        label.snp.makeConstraints { $0.edges.equalToSuperview() }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

fileprivate func makeLabel(color: UIColor) -> UILabel {
    let label = UILabel()
    label.text = Array(repeating: "LABEL", count: 100).joined()
    label.textAlignment = .center
    label.numberOfLines = 0
    label.textColor = .white
    label.backgroundColor = color
    return label
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む