20200315のiOSに関する記事は10件です。

シワ(Sign in with Apple) 対応

Sign in with Appleとは

Apple ID を利用したサインイン方法。
サードパーティーログインを使用しているアプリは2020/04までにSign in with Appleに対応する様に義務化した。

App Storeに提出されるすべての新しいAppやアップデートは、2020年4月30日までにこれらのガイドラインに従う必要があります。
引用元:https://developer.apple.com/jp/news/?id=03042020d

そうです。いつものAppleの無茶振りです。
対応しないとリジェクトされてしまう為、仕方なく実装する事に、、
まずは仕様から

アプリへの公開情報

アプリに公開される情報は大きく分けて以下の2つです。

  • 名前
  • メールアドレス
    • 公開メールアドレス
    • プライベートメールアドレス

これらの値はSign in with Apple新規登録時かユーザーがアプリと紐付け解除を行った後の初回のみ設定する事が出来ます。

名前

アプリに連携する名前はアプリ毎にユーザーが設定することができます。
デフォルトではiCloud アカウントに設定している姓名が入ってきますが、公開したくない場合は任意の名前に変更可能になっています。

公開メールアドレス

ユーザーがiCloudの登録に使用しているメールアドレス。
* 実際のメールアドレスなので転送を停止する機能などは今回対象外

プライベートメールアドレス

認証時にユーザーがメールアドレス(Apple ID)を非公開設定すると、Appleが自動生成したランダムな英数字のメールアドレスをアプリ(サービス)に連携する。その結果ユーザーは自身のメールアドレスを公開する事なくアプリでログインが可能になる。
* アプリとのメールやりとりはAppleが アプリ→自動生成メールアドレス→プライベートメール という形でメールを転送してくれる。(設定の必要あり)
* ユーザーはメールの転送を停止したり、転送先のメールアドレスを変更する事が出来る。

このプライベートメールアドレスは出来るとユーザーにとって何が嬉しいのか?
サービス登録時に実際のメールアドレスを使用するとサービスの利用を停止した後にもメールアドレスを保持されて悪用されるリスクがある。そこでこのプライベートメールアドレスを使用すると実際のメールアドレスはサービスに公開せず、利用停止したい場合には紐付け解除(プライベートメールアドレスの破棄)を行えば悪用されなくなるというメリットがある。

アプリとの紐付けの解除

iCloudの管理画面からアプリとの紐付け解除を行うことが出来る。
ユーザーにとって

※iOS13以上では[設定>Apple ID(一番上の箇所)>パスワードとセキュリティ>Apple IDを使用中のApp]からでも解除出来る

CSRF対策

Sign in with AppleはCSRF対策の為にAppleの認証APIを実行する際にstateを指定することができる。
簡単に言うとなりすまし防止の為にアプリで送信時に作成したstateとAppleの認証時に返されたstateが一致しているかを確認する。

完璧に仕様が分かったことで対応する事を以下に記載。

対応内容

あくまでアプリが対応する内容を記載する。

  • コーディング
    • iOS13以上の対応
    • iOS12未満の対応(必須ではない)
    • Android対応(必須ではない)
  • Apple Developerでの設定
    • Sign in with Appleの有効化
    • メールのホワイトリスト追加
    • ServiceIDの登録
  • Xcodeの設定
    • Sign in with Apple有効化
  • デザイン対応
    • ガイドラインに沿ったデザイン

iOS13での対応

Appleのログインボタンを生成する。

LoginViewController.swift
    func setupProviderLoginView() {
        let authorizationButton = ASAuthorizationAppleIDButton()
        authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
        self.loginProviderStackView.addArrangedSubview(authorizationButton)
    }

ユーザーの姓名とメールアドレスを受け取る様な認証のリクエストをAppleに対して行う。

LoginViewController.swift
    @objc
    func handleAuthorizationAppleIDButtonPress() {
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]

        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
    }

Delegateで結果を受信する。

LoginViewController.swift
extension LoginViewController: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            // 成功した場合       
            let userIdentifier = appleIDCredential.user
            let fullName = appleIDCredential.fullName
            let email = appleIDCredential.email
        }
    }

    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // エラーが発生した場合
    }
}

iOS12以下での対応

WebViewを利用してApple JSに対応する必要があります。
サーバサイドの実装が必要だったりAppleボタンを自作しなければならなかったりするので
出来ればiOS13以降のみの対応の方が無難だと思います。

デザイン対応

Appleのガイドラインの記載を抜粋。
* Appleのボタンを目立つようにする
* 他サードパーティのログインボタンよりも小さくしないこと
* スクロールしないでもAppleのボタンが表示されるようにする
* ボタンの色は背景色と被らないようにする
などなど

ガイドラインに従わないとリジェクトされます。
実際に初めてSign in with Apple対応した時にはボタンと背景の色やフォントサイズなどでリジェクトされました。
(iOS12対応の為にボタンは自作しました)

https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/

まとめ

iOS12以下に対応する為には、自作したボタンをAppleのデザインに合わせたり認証をWebViewで行ったりしないとならなく別対応が必要なので骨が折れますが、
iOS13のみなら基本Appleが提供しているSDKを使用すれば比較的簡単に開発が済みます。
最近(2020/03時点)リジェクトが厳しくなっている気がするのでデザインのガイドラインなどはしっかり読んで対応した方が良いです。

参考URL

* サンプルコード
https://developer.apple.com/documentation/authenticationservices/adding_the_sign_in_with_apple_flow_to_your_app

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

ライブラリ作ったから見てくれ

申し訳ありません。「見て下さい。」ですね。
調子乗りました。

気を取り直して

こんにちはこんばんは。初投稿のいるべです。
昨年クリスマスに独り身でしにたくなったのでswiftの勉強を始めました。
情報系の学部にいながらプログラミングとは関わらない生活をしていたのでプログラミング歴は実質3ヶ月程となります。

今、自分のお気に入りのものを教えてよって主旨のアプリを作成してます。SNSは素人には無理ゲーとか言う声が聞こえてきそうですがまぁやるだけタダなので黙ってて下さい。

今回は表題の通り。そのアプリを作る上で欲しくなった機能をライブラリとして作成したので、「まぁ見てって下さいよ」というところでつらつらと書かせて下さい。
ただの宣伝にならないように色々と書けたらいいなと思います。

まず前提。なんでこんなものを作ったか、だ。

いやいや、その「こんなもの」を先に言えよと思う方もいらっしゃるかも知れんがスルーします。

UICollectionViewって意味分かんなくね?

玄人の皆様方に置かれましては何が?と思われるかも知れないが、
- まずdelegateが意味わからん。
- flowlayout何それ??
- そもそもRunさせても何も表示されん、、
その他壁は沢山あると言っても過言では無い。(断言)
少なからず全く調べずにcollectionViewを使って意図した挙動を実装することができる人はそう多く無いのではと考えている(え?みんなふつーに書けるの、、?)

みんな悩んでるやろし作ったろ!

まぁもちろん既に上位互換ライブラリは存在していますが経験です。沢山勉強になりました。
僕と同じような初心者の皆様方も、「既にあるからそれ使おう」「既にあるから作るのやめよう」と言うのは悪くも無いけど良くも無いことがあるということを覚えておきましょう。たまには遠回りも必要ということです。

お待たせたしました。

今回作ったものはこちらになります。
CheckableTagImage.jpg
と画像をのっけてもインパクトがない。。
真ん中下のネコを妹に描いてもらった。ほんの1分程度でさらっと。すごい。

タグを簡単に生成したいやんってことでCheckableTagというライブラリを作成しました。

簡単に説明しますと、簡単にタグを作成できるライブラリです。
更に、タップしたら色が変わるようになっているのでチェックボックスのような使い方もできます。 
画像を見ての通り、タグの形にもいくつかあるので選ぶのに悩んじゃいますね?(☝︎ ՞ਊ ՞)☝︎

導入

現在はPodのみの対応です。

pod 'CheckableTag'
を追加してpod installして下さい。

使い方

import UIKit
import CheckableTag

class ViewController: UIViewController {

    let items = ["Hello"]

    let tagView: CheckableTag = {
        let view = CheckableTag()
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        tagView.frame = self.view.bounds
        self.view.addSubview(tagView)
        tagView.dataSource = self

    }
}

extension ViewController: CheckableTagDataSource {
    func getSelected(sender: CheckableTag) -> [Bool]? {
        return nil
    }
    func getItems(sender: CheckableTag) -> [String] {
        return items
    }
}

最低限のコードはこれだけです。
これで、下のような感じになります。

IMG_3691.jpg

*トリミングしてあります。
タップすると、
IMG_3692.jpg
色が変わります。

プロパティ

まぁここは興味があれば自由に弄ってみて下さい。
いるべ的には下記の設定が好きです。

//tagの形を変更
tagView.cellType = .curve
//tagの表示スタイルを変更
tagView.cellStyle = .groove
//ユーザが選択可能かどうか指定
tagView.canSelected = false
//フォントサイズの指定。fontsizeによってtagのサイズが変わる。
tagView.fontSize = 20

色の変更

/// 選択状態の色を指定する
/// - Parameters:
///   - text: テキストカラー
///   - back: バックグラウンドカラー
///   - line: 枠線カラー
tagView.setSelectedColor(text: .white, back: .red, line: .black)

/// 非選択状態の色を指定する
/// - Parameters:
///   - text: テキストカラー
///   - back: バックグラウンドカラー
///   - line: 枠線カラー
tagView.setUnSelectedColor(text: .gray, back: .white, line: .gray)

選択状態の取得

//存在する全てのタグの選択状態を取得します。
//trueのとき選択されている
//falseのとき非選択状態。
let isSelectedArray: [Bool] = tagView.getIsSelected()

細かいことを指定する場合

DataSourceの使用例

tagView.dataSource = self

------------------------------

extension ViewController: CheckableTagDataSource {
    ///初期選択状態を指定できる。
    ///canSelect = falseにしてこれと組み合わせればタグになる。
    func getSelected(sender: CheckableTag) -> [Bool]? {
        return [true, false, true, false, true, true]
    }

    ///tagを生成するためにタグのテキストを伝えられる。
    func getItems(sender: CheckableTag) -> [String] {
        return items
    }
}

Delegateの使用例

tagView.delegate = self

----------------------------

extension ViewController: CheckableTagDelegate {
    ///セルを選択したときの動作を設定できる。
    func didSelected(cell: CheckableCellProtocol) {
        print(cell.textLabel.text)
    }
}

Animationの使用例

tagView.animation = self

----------------------------

///このアニメーションは意味不明なのでただの参考として扱って下さい。
extension ViewController: TouchCellAnimationProtocol {
    ///タップし始めのときに行われるアニメーション。
    func touchStartAnimation(cell: CheckableCellProtocol) {
        UIView.transition(with: cell, duration: 1.0, options: [.transitionFlipFromTop], animations: nil, completion: nil)
    }
    ///タップ終わりのときに行われるアニメーション。
    func touchEndAnimation(cell: CheckableCellProtocol) {
        UIView.transition(with: cell, duration: 1.0, options: [.transitionFlipFromBottom], animations: nil, completion: nil)
    }
}

選択状態について

collectionViewやtableViewの選択状態を管理する時にスクロールしたら選択状態がずれていることがあります。
これはセルごとで選択状態を管理するのではなく、セル全ての選択状態を管理する配列を用意してそれを読んであげればそんな問題とはおさらばです。

///collectionviewを使っている上位view
    //cellに代入するテキストの配列。
    private(set) var items: [String] = []
    //cellの選択状態を管理する配列
    public lazy var isSelectedItems: [Bool] = {
        return [Bool].init(repeating: false, count: items.count)
    }()
///cellが生成されるタイミング
/// UICollectionViewDataSourceを継承

public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    //cellを再利用するために引っ張り出してくる
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)

    //選択状態を管理している配列から、今扱っているcellに該当する選択状態を見て、それに見合ったスタイルを反映する。
    if isSelectedItems[index.row] {
        cell.selectedColor(text: textColor, back: backColor, line: lineColor)
    } else {
        cell.unSelectedColor(text: textColor, back: backColor, line: lineColor)
    }

    return cell
}

Enumは便利。

Enumってすごいですね。僕は気持ちが先走ってよくタイポをしてしまう人間なのですが,
予測変換で出てこればタイポもないし、後からの要素の追加も楽々!今回はEnumと後述するProtocolに救われました。

public enum CellType: String {
    case square = "SquareCheckableCell"
    case curve = "CurveCheckableCell"
    case round = "RoundCheckableCell"
    case circle = "CircleCheckableCell"
}

これは実際のコードです。Stringがうだうだしてますね。
タイポしやすい文字列も
cellType.rawValue

を使えばここで設定している文字列が取得できますからタイポが一気に減ります。

最初はcase squareだけだったんですが、一度一通り作って仕舞えばもう簡単!
case *** と新しい要素をここに追加してビルドします(おい)
もちろんビルドは通りません。

そのビルドに対して出たエラーのところを修正すればあら不思議。
もうまた別の要素が使えるようになっているではないですか。

///指定されたcellのタイプを返す。
public func getCellType(collection: UICollectionView, index: IndexPath) -> CheckableCellProtocol {
        switch cellType {
        case .square:
            return collection.dequeueReusableCell(withReuseIdentifier: cellType.rawValue, for: index) as! SquareCheckableCell
        case .curve:
            return collection.dequeueReusableCell(withReuseIdentifier: cellType.rawValue, for: index) as! CurveCheckableCell
        case .round:
            return collection.dequeueReusableCell(withReuseIdentifier: cellType.rawValue, for: index) as! RoundCheckableCell
        case .circle:
            return collection.dequeueReusableCell(withReuseIdentifier: cellType.rawValue, for: index) as! CircleCheckableCell
        }
    }

今回はご覧の通り似てるけど違うものをいくつも作ったのでstrategyパターンを採用しました。(実は後付け)
これとenumのおかげで今後も変更がだいぶ楽になりそうです。

Protocolくんありがとう。

protocolの何が便利かというと後からの変更がとても楽です。追加も楽です。

protocolで決まりを作っておけば後はボーッとコーディングできる。
import Foundation
import UIKit

public protocol CheckableCellProtocol: UICollectionViewCell {
    ///cellの細かな表示を変更する
    var cellStyle: CellStyle { get set }
    ///cellとcontentViiewの間のマージン
    var margin: CGFloat { get }

    ///collectionviewに最初からあるview
    var contentView: UIView { get }
    ///textを表示するために一番中心に配置されるview
    var textLabel: UILabel { get }

    ///アニメーションを行うためのプロトコル
    var animationProtocol: TouchCellAnimationProtocol! { get set }
    ///labelに表示するテキストをセットする
    func setTextToTextLabel(textName: String)
    ///選択時の色設定
    func selectedColor(text textColor: CellColor?, back backColor: CellColor?, line lineColor: CellColor?)
    ///非選択時の色設定
    func unSelectedColor(text textColor: CellColor?, back backColor: CellColor?, line lineColor: CellColor?)

    ///cellを設定
    func setCell()
    ///normalstyleの設定。cellにぴったりくっつく
    func setNormalStyle()
    ///groovestyleの設定。cellとの間に少し溝を作る
    func setGrooveStyle()
}

///共通してるものはもう作っておく
extension CheckableCellProtocol {

    public var margin: CGFloat {
        return LayoutConstants.margin
    }

    public func setTextToTextLabel(textName: String) {
        textLabel.text = textName
    }

    public func selectedColor(text textColor: CellColor?, back backColor: CellColor?, line lineColor: CellColor?) {
        textLabel.textColor = textColor?.selectedColor ?? .white
        contentView.backgroundColor = backColor?.selectedColor ?? .init(red: 255 / 255, green: 100 / 255, blue: 100 / 255, alpha: 1)
        self.layer.borderColor = lineColor?.selectedColor?.cgColor ?? UIColor.gray.cgColor
    }

    public func unSelectedColor(text textColor: CellColor?, back backColor: CellColor?, line lineColor: CellColor?) {
        textLabel.textColor = textColor?.unSelectedColor ?? .gray
        contentView.backgroundColor = backColor?.unSelectedColor ?? .init(red: 230 / 255, green: 230 / 255, blue: 230 / 255, alpha: 1)
        self.layer.borderColor = lineColor?.unSelectedColor?.cgColor ?? UIColor.gray.cgColor
    }

    public func setCell() {
        switch cellStyle {
        case .normal:
            setNormalStyle()
        case .groove:
            setGrooveStyle()
        }
    }
    public func setNormalStyle() {
        textLabel.frame = contentView.bounds
    }
    public func setGrooveStyle() {
        contentView.frame = CGRect(x: self.bounds.minX + margin, y: self.bounds.minY + margin, width: self.bounds.width - margin * 2, height: self.bounds.height - margin * 2)
        textLabel.frame = contentView.bounds
    }

}

protocolのextensionを使えば、必ず同じな部分の実装を楽に共通化できるのでとても便利。まぁclassを継承しても良いんだけどprotocolは直接インスタンス化できないところが好き。

ストラテジーパターン

ストラテジーパターンを使うと同じようなものをいくつも作成する時に追加が楽。変更も楽。
やり方は、ルール(プロトコル)を作ってそのルールに則って種類の分かれる奴らをそれぞれ作る。今回で言えばCheckableCellProtocolを作ってそれに則ったcellをSquare, Curve, Round, Circleと作った。それらを利用する側は外部からそのプロトコルに則ったインスタンスを貰うかそれを特定できる情報をもらうようにすれば完成。後は簡単に追加削除変更なんでもござれ。
具体的には上記のgetCellType関数が肝になっているのでgithubから見てみてね。

さてこのライブラリの完成度は如何程か?

良かった点

  • 割と使いやすいのでは?
  • 変更しやすかった。楽しくコーディングできた。
  • 自分の理想にかなり近い。

改善点

  • はいまずUITestが通りません。意味がわかりません。調べたところdelegateが動作していないのが原因っぽいのですがだからと言ってどうすれば解決するのかさっぱりわからない。要勉強ですね。
  • delegateでタグ同士の間のスペースとか指定できるようにしたい。
  • github全部ニホンゴ。
  • コードレイアウトマンなのでstoryboardで使えるか確認していない。。(近日中に必ず確認して使えるようにするつもりではあるが)
  • CellTypeもCellStyleももっと種類増やしたい。
  • タグ左寄せの設定もできるようにしたい。

自己評価は120点!良くがんばりました。これからも頑張りましょう!(文句言うな。)

初心者をはじめこの記事を読んで下さった皆様方。

軽い感じで読みやすくしようと思って書いていましたがそうでもなくなってしまいましたね。
もっと色々書きたかったけど難しいし体力切れや。
飛ばし飛ばしでもここまで読んで下さってありがとうございました。解説の部分はあんまり参考にならなかったと思います。定期的にアウトプットしてしっかり言語化することが大事だと感じました。
改善点はまだまだありますがぜひ一度使ってみて下さい。そしてissueやPR投げてくださると嬉しいです。

なぜ「初心者をはじめ」と書いたかと言いますと、このコードは簡単なコードで書かれています。まぁ初心者が書いている+他人のコードなので読みにくいかも知れませんが。もっとプログラミング出来るようになりたいと考えている方は、OSSにPR投げたりしてcontributionを重ねていくことって大事だと思うのでそれのとっかかりとして、まずこのライブラリにPRを投げるのも良いと思うよってことが言いたかったです。
READMEのちょっとした修正やタイポ修正などでもこちらとしては本当に助かりますしいわゆるwin-winですね。(古い?)こんなのどうや?って気軽に投げて下さい。

※この記事の内容は変なこと言っている可能性も大いにあります。情報の取捨選択をうまく行って、僕には文句ではなく指摘をしてくださると踊って喜びます。

長文駄文失礼しました。ありがとうございました。

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

swift コードで単色の画像を生成する方法

var image: UIImage? {
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
        UIGraphicsBeginImageContext(rect.size)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        context.setFillColor(UIColor(red: 1, green: 1, blue: 1, alpha: 0.8).cgColor)
        context.fill(rect)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode11でUIScrollViewのAutoLayoutの制約をかける

はじめに

UIScrollViewのAutoLayoutの制約をかける場合、かけ方にクセがあり、かつ作業途中でエラーが発生するため、少し手こずるんですよね。
ここではXcode11でUIScrollViewのAutoLayoutの制約をかける方法を記載します。

チュートリアル

環境

  • Xcode11

手順

1. ScrollViewを配置

ViewControllerのView上にScrollViewを配置します。
スクリーンショット 2020-03-15 14.39.08.png

2. ScrollViewにAutoLayoutの制約をかける

ViewController上のSafeAreaとScrollViewの両方を選択します。
スクリーンショット 2020-03-15 15.12.24.png
画面右下のAlignボタンを選択し、Top/Bottom/Trailling/Leadingの4辺に制約をかけます。
ここでは0を設定します。
スクリーンショット 2020-03-15 15.26.18.png
かけた後IB上でエラーが発生しますが、一旦無視して下さい。
スクリーンショット 2020-03-15 14.44.39.png

3. Viewを配置

ScrollView上にViewを配置します。
ここではScrollViewとの違いを分かりやすくするため、Viewの背景色を赤色に設定しています。
スクリーンショット 2020-03-15 15.00.33.png

4. ViewのAutoLayoutの制約をかける

ScrollViewのContentLayoutGuideとViewの両方を選択し、画面右下のAlignボタンから、Top/Bottom/Trailling/Leadingの4辺に制約をかけます。
スクリーンショット 2020-03-15 16.12.43.png
次にViewを選択しながら右クリックでScrollViewのFrameLayoutGuideへドラッグ&ドロップし、Equal Widthを選択します。
スクリーンショット 2020-03-15 16.16.34.png
最後にViewを選択し、画面右下のAdd new constraintsボタンから、Heightに制約をかけます。
ここでは1000を設定します。
スクリーンショット 2020-03-15 16.24.24.png

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

iOSアプリを作って公開してみたいと思う

タイトル通りです。Swift初心者がアプリを公開するまでに躓いたところを記録していきたいと思います。

現時点の著者スペック:
2年前にSwiftにチャレンジしようとして挫折。その後、真面目に勉強した言語はPythonのみ。ここ数週間Swift文法本を読むなかで「構造体」などのPythonになかった概念で頭がいっぱいになっている。

作りたいアプリ:
1. iPhoneアプリ
 Youtube、ニコニコ動画をみながらブラウザを使えるようにしたい。(同じ発想のアプリは公開されているけど、現時点ではDLはできるけど動かなくなっている) https://apps.apple.com/jp/app/netube/id937049763
 
2. Macアプリ
 ブラウザ閲覧履歴から、各サイトの関係性を自動的に時系列でまとめて一覧表示してくれるようなアプリが欲しい。レポートを書くのに便利そうなので。(これについてはSwiftで作るのが良いのかすらよくわかってない。)

Apple Developer税も払い、準備完了です。
20.20.png

次回記事 ブラウザアプリ作り(1) とりあえず動く状態にする

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

iOSを作って公開してみたいと思う

タイトル通りです。Swift初心者がアプリを公開するまでに躓いたところを記録していきたいと思います。

現時点の著者スペック:
2年前にSwiftにチャレンジしようとして挫折。その後、真面目に勉強した言語はPythonのみ。ここ数週間Swift文法本を読むなかで「構造体」などのPythonになかった概念で頭がいっぱいになっている。

作りたいアプリ:
1. iPhoneアプリ
 Youtube、ニコニコ動画をみながらブラウザを使えるようにしたい。(同じ発想のアプリは公開されているけど、現時点ではDLはできるけど動かなくなっている) https://apps.apple.com/jp/app/netube/id937049763
 
2. Macアプリ
 ブラウザ閲覧履歴から、各サイトの関係性を自動的に時系列でまとめて一覧表示してくれるようなアプリが欲しい。レポートを書くのに便利そうなので。(これについてはSwiftで作るのが良いのかすらよくわかってない。)

Apple Developer税も払い、準備完了です。
20.20.png

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

Android/iOSストア情報まとめ(2020.03)

はじめに

アプリを新規公開する際に必要な情報をまとめました。
ストア情報はデザイナーや非エンジニアが決めることが多いため、そのような方たちが読めるようにまとめます。また、ストア情報の他にも、決めるべき項目について記載しました。

※オプショナルな情報については薄い文字で記載しています。

アプリ情報

Android iOS 備考
正式名称 ~50文字 ~23文字 SEO対策しすぎるとリジェクトされる可能性があります
短縮名称 ~12文字程度 ~13文字 アプリアイコンの下に表示される名前。
機種などにより表示できる文字数は変わります
サブタイトル なし ~30文字 iOS11以降で表示されます
更新情報 ~500文字 ~4000文字 初回リリース時も必要となります
ID Package id Bundle id Androidの場合、ストアURLに含まれます。
一度決めると変更はできません。例えばGoogleMapの場合はcom.google.android.apps.mapsとなっています

忘れがちなのが更新情報です。初回リリースですが、こちらの文言も必要となりますのでご注意ください。
また、プライマリー言語の設定と、各項目の翻訳も必要となります。

更新情報について
AndroidのGoogleフォトのスクリーンショットです。下記のように最新ニュースとして更新情報が表示されます。
スクリーンショット 2020-03-15 22.58.12.png

IDについて
AndroidのPackageIdはURLに表示されます。下記はGoogleMapの例です。
image.png
そのため、あまり適当なIDを付与しないようにします。一度リリースすると変更もできなくなります。
一方、iOSの場合URLに表示されるのはAppleから自動で割り振られるID(数字)のため、一般ユーザが目にすることはありません。が、管理上、Androidのpackage idに似せた方がよいと思います。

説明文

Android iOS 備考
詳しい説明 ~4000文字 ~4000文字
簡単な説明 ~80文字 ~170文字 iOSではプロモーション用テキスト
キーワード なし ~100文字 SEO対策
  • プロモーションテキストについて
    • iOS11以降。審査なしで現在のアプリ機能について知らせることができるようです。

ストア画像(Androidのみ)

サイズ 形式 用途
フィーチャーグラフィック 横1,024x縦500 JPG or 24bit PNG(アルファなし) ストアのアプリタブでフィーチャーされる際に使用。
必須
1.png
プロモーション画像 横180x縦120 JPG or 24bit PNG(アルファなし) ストアのゲームタブでフィーチャーされる際に使用。
オプショナル。
2スクリーンショット 2020-03-15 23.38.23.png

URL、連絡先情報

Android iOS 備考
プラポリURL オプショナル 必須
サポートURL なし オプショナル
ウェブサイト(Android)/ マーケティングURL(iOS) オプショナル オプショナル
Copyright なし 必須
電話 オプショナル なし ストアに表示されます
メール 必須 なし ストアに表示されます

アプリアイコン

OSごとに大きく異なるため、別々に記載します。

Android

ストア用画像

  • 512x512
  • 32bit PNG

Android8.0未満

解像度 アイコンサイズ
xxxhdpi 192x192
xxhdpi 144x144
xhdpi 96x96
hdpi 72x72
mdpi 48x48

AdaptiveIcon(Android8.0以上)

参考:AdaptiveIconとは

解像度 サイズ
xxxhdpi 432x432
xxhdpi 324x324
xhdpi 216x216
  • foregroundとbackgroundの2枚を用意します(サイズは同じ)
  • foregroundは直径66がsafe zoneなので、モチーフはそこに収まるようにします
  • OS8以上なので、ある程度高解像度の画像だけで十分

iOS

サイズ 用途
20x20 ipad notification用(@1x)
29x29 iPad設定画面用(@1x)
iPhone設定画面用(@1x)
40x40 ipad notification用(@2x)
notification用(@2x)
iPad Spotlight用(@1x)
58x58 iPad設定画面用(@2x)
iPhone設定画面用(@2x)
60x60 notification用(@3x)
76x76 iPadAppIcon(@1x)
80x80 iPad Spotlight用(@2x)
Spotlight用(@2x)
87x87 設定画面用(@3x)
152x152 iPadAppIcon(@2x)
120x120 Spotlight用(@3x)
AppIcon(@2x)
167x167 iPadProAppIcon(@2x)
180x180 AppIcon(@3x)
1024x1024 ストア用

スクリーンショット

スマートフォンの場合のみ記載します。必要に応じてタブレット、Wear、TVなども設定可能です。
また、各言語でローカライズされた画像を用意しましょう。

Android iOS
サイズ 1 辺の最小の長さ: 320px
1 辺の最大の長さ: 3840px
アス比は1:2以下(縦長すぎ/横長すぎるのはエラー)
後述
形式 JPG/PNG(アルファなし) JPG/PNG(アルファなし)
枚数 2枚(最大8枚) 各サイズ最低1枚、最大10枚

iOSの場合は以下のサイズで揃える必要があります。

サイズ 枚数 該当端末
6.5インチ 1242 x 2688 最低1枚、最大10枚 iPhone 11 Pro Max, iPhone 11, iPhone XS Max, iPhone XR
5.5インチ 1242 x 2208 最低1枚、最大10枚 iPhone 6s Plus、iPhone 7 Plus、iPhone 8 Plus
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android/iOSストア情報まとめ(2020年03月更新)

はじめに

アプリを新規公開する際に必要な情報をまとめました。
ストア情報はデザイナーや非エンジニアが決めることが多いため、そのような方たちが読めるようにまとめます。また、ストア情報の他にも、決めるべき項目について記載しました。
とはいえ、本記事では基本的に必要最低限に近いレベルのことしか記載していませんので、実際には様々なカスタマイズ情報を掲載してアプリのグロースをしてみてください。

※オプショナルな情報については薄い文字で記載しています。

アプリ情報

Android iOS 備考
正式名称 ~50文字 ~23文字 SEO対策しすぎるとリジェクトされる可能性があります
短縮名称 ~12文字程度 ~13文字 アプリアイコンの下に表示される名前。
機種などにより表示できる文字数は変わります
サブタイトル なし ~30文字 iOS11以降で表示されます
更新情報 ~500文字 ~4000文字 初回リリース時も必要となります
ID Package id Bundle id Androidの場合、ストアURLに含まれます。
一度決めると変更はできません。例えばGoogleMapの場合はcom.google.android.apps.mapsとなっています

忘れがちなのが更新情報です。初回リリースですが、こちらの文言も必要となりますのでご注意ください。
また、プライマリー言語の設定と、各項目の翻訳も必要となります。

更新情報について

下記のように最新ニュース新機能として更新情報が表示されます。
Android iOS
スクリーンショット 2020-03-15 22.58.12.png image.png

IDについて
AndroidのPackageIdはURLに表示されます。下記はGoogleMapの例です。
image.png
そのため、あまり適当なIDを付与しないようにします。一度リリースすると変更もできなくなります。
一方、iOSの場合URLに表示されるのはAppleから自動で割り振られるID(数字)のため、一般ユーザが目にすることはありません。が、管理上、Androidのpackage idに似せた方がよいと思います。
ちなみにiOSのストアURLはこのようになります。
image.png

説明文

Android iOS 備考
詳しい説明 ~4000文字 ~4000文字
簡単な説明 ~80文字 ~170文字 iOSではプロモーション用テキスト
キーワード なし ~100文字 SEO対策
  • プロモーションテキストについて
    • iOS11以降。審査なしで現在のアプリ機能について知らせることができるようです。

ストア画像(Androidのみ)

サイズ 形式 用途
フィーチャーグラフィック 横1,024x縦500 JPG or 24bit PNG(アルファなし) ストアでフィーチャーされる際に使用。
必須スクリーンショット 2020-03-15 23.20.05.png
プロモーション画像 横180x縦120 JPG or 24bit PNG(アルファなし) 古い端末のストアでフィーチャーされる際に使用されます。今となっては用意しなくてもよいと思います

フィーチャーグラフィックはかつてストアのヘッダー画像として使われていましたが、ストアの仕様変更により、フィーチャー時にのみ使われるようになりました。

URL、連絡先情報

Android iOS 備考
プラポリURL オプショナル 必須
サポートURL なし オプショナル
ウェブサイト(Android)/ マーケティングURL(iOS) オプショナル オプショナル
Copyright なし 必須
電話 オプショナル なし ストアに表示されます
メール 必須 なし ストアに表示されます

URL、連絡先情報の表示場所
Android iOS
スクリーンショット 2020-03-15 22.59.09.png Screen Shot 2020-03-16 at 0.13.16.png

アプリアイコン

OSごとに大きく異なるため、別々に記載します。

Android

Androidはストア用に高解像度が1枚、アプリに組み込む画像が解像度ごとに必要です。また、OS8以降とその前でアイコンの仕様が異なります。

ストア用画像

  • 512x512
  • 32bit PNG

Android8.0未満

解像度 アイコンサイズ
xxxhdpi 192x192
xxhdpi 144x144
xhdpi 96x96
hdpi 72x72
mdpi 48x48

AdaptiveIcon(Android8.0以上)

参考:AdaptiveIconとは

解像度 サイズ
xxxhdpi 432x432
xxhdpi 324x324
xhdpi 216x216
  • foregroundとbackgroundの2枚を用意します(サイズは同じ)
  • foregroundは直径66がsafe zoneなので、モチーフはそこに収まるようにします
  • OS8以上なので、ある程度高解像度の画像だけで十分

iOS

サイズ 用途
20x20 ipad notification用(@1x)
29x29 iPad設定画面用(@1x)
iPhone設定画面用(@1x)
40x40 ipad notification用(@2x)
notification用(@2x)
iPad Spotlight用(@1x)
58x58 iPad設定画面用(@2x)
iPhone設定画面用(@2x)
60x60 notification用(@3x)
76x76 iPadAppIcon(@1x)
80x80 iPad Spotlight用(@2x)
Spotlight用(@2x)
87x87 設定画面用(@3x)
152x152 iPadAppIcon(@2x)
120x120 Spotlight用(@3x)
AppIcon(@2x)
167x167 iPadProAppIcon(@2x)
180x180 AppIcon(@3x)
1024x1024 ストア用

スクリーンショット

スマートフォンの場合のみ記載します。必要に応じてタブレット、Wear、TVなども設定可能です。
また、各言語でローカライズされた画像を用意しましょう。

Android iOS
サイズ 1 辺の最小の長さ: 320px
1 辺の最大の長さ: 3840px
アス比は1:2以下(縦長すぎ/横長すぎるのはエラー)
後述
形式 JPG/PNG(アルファなし) JPG/PNG(アルファなし)
枚数 2枚(最大8枚) 各サイズ最低1枚、最大10枚

iOSの場合は以下のサイズで揃える必要があります。

サイズ 枚数 該当端末
6.5インチ 1242 x 2688 最低1枚、最大10枚 iPhone 11 Pro Max, iPhone 11, iPhone XS Max, iPhone XR
5.5インチ 1242 x 2208 最低1枚、最大10枚 iPhone 6s Plus、iPhone 7 Plus、iPhone 8 Plus
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] 同一ドメインでのユニバーサルリンク(Universal Links)は動作しないので注意メモ

Web側とiOS側でユニバーサルリンクを設定したが何故か動作せず、調べたら同一ドメイン上では機能してくれない様でした。。
実装前の調査不足ということで、備忘録的なメモになります。

概要

  • 同一ドメインではユニバーサルリンクは機能しない(アプリが起動しない)
  • サブドメインを使った代替策

同一ドメイン上ではアプリへ遷移ができない

ユニバーサルリンクは同一ドメイン上では動作しないため注意が必要です。
公式ドキュメントでも注意点として記載されていました。。
すでにWebで利用しているユーザーに対して急にアプリ起動したらUX的によろしくないのでAppleさんが制御しているらしいです。
(翻訳した自分の解釈が間違っていなければ)

Support Universal Links:
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html

サブドメインをリンク先に設定することで、ユニバーサルリンクを有効にすることができます。
とはいえ、サブドメインで分けるほどのページではないがユニバーサルリンクを有効にしたい場面があるかと思います。
次では、見た目上は同一ドメインでユニバーサルリンクが動作する例を紹介します。

代替策

同一ドメインではユニバーサルリンクは無効ですが、サブドメインなどに遷移先を設定すれば有効になります。
以下例では、見た目上は同一ドメインでの遷移ですがユニバーサルリンクをサブドメインとしている代替策になります。

# shop一覧画面(www.hoge.com)から詳細画面を開く場面を想定しています。
# 詳細画面のリンク先をサブドメイン(shop.hoge.com/shop_id)として設定。
# リンク先のサブドメインはリダイレクト先として詳細画面(www.hoge.com/shop/shop_id)を設定。
# 設定したリンクはユニバーサルリンクとして設定しているため、アプリが起動します。
# アプリ未インストール&Webでの表示では、リダイレクト先の画面(www.hoge.com/shop/shop_id)が表示されます。

image.png

まとめ

  • ユニバーサルリンクは同一ドメインでは機能しない
  • 機能させるためには、サブドメインなど活用する(場面に応じてリダイレクト先を設定するなど対応する)
  • ユニバーサルリンクを使用しない方法(URLスキーム)で実装できないかを考える

記載した情報に誤り等ございましたら、ご指摘をいただけると幸いですmm

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

Swift UI で SearchBar を使う

Search Bar をつけたい

SwiftUI でUISearchBarをつけようとすると、存外に苦労しました。おそらくまだ十分にAPIが開放されていないためだと思います。こういう場合、UIViewControllerRepresentableなどを使い、SwiftUIで使えるようにしていくしかないのかなぁと思います。やりたいのは以下の通り、UIKit使ってたら普通にできたよね・・・?
p.png

tl;dr

  • SearchBarを使いたい時はUINavigationControllerをUIViewControllerRepresentableでラップする。その際にUISearchControllerをセット
  • NavigationBarしか使用しない場合は、親ViewのSafeAreaをedgesIgnoringSafeArea(.vertical)で調整
  • TabBarをでNavigationBarを囲っている場合は、親ViewのSafeAreaをedgesIgnoringSafeArea(.top)で調整後、NavigationBarが実装されているViewのSafeAreaをedgesIgnoringSafeArea(.vertical)で調整する

1. SearchBarをSwiftUIで使用する

  • まずUINavigationViewControllerにUISearchControllerを追加して、それををSwiftUIで使用できるようにします。
struct NavigationViewControllerRepresentation: UIViewControllerRepresentable {
    typealias UIViewControllerType = UINavigationController

    func makeUIViewController(context: Context) -> UINavigationController {
        let viewController = UIHostingController(rootView: List{
            Text("one").frame(height: 120.0)
            ...
            Text("six").frame(height: 120.0)
        }.navigationBarTitle("Title"))

        let navigationController = UINavigationController(rootViewController: viewController)
        navigationController.navigationBar.prefersLargeTitles = true

        viewController.navigationItem.searchController = UISearchController()
        return navigationController
    }

    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {

    }
}
  • SwiftUI Canvasに表示させる
MyNavigationView.swift
struct MyNavigationView: View {
    var body: some View {
        NavigationViewControllerRepresentation()
    }
}

struct MyNavigationView_Previews: PreviewProvider {
    static var previews: some View {
        MyNavigationView()
            .environment(\.colorScheme, .dark) // わかりやすくするためにダークモードで表示
    }
}

2. SafeAreaを調整する

Screenshot 2020-03-14 at 15.11.49.png

この状態だと、赤枠で囲ったように上下に隙間ができます。SafeAreaですね。MyNavigationView.edgesIgnoringSafeArea(.vertical)としたいところですがここではその衝動をぐっと抑えます。

SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {

        let contentView = MyNavigationView().edgesIgnoringSafeArea(.vertical) // [1]

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

コメントの[1]にあるように、最下層のビューに対してedgesIgnoringSafeArea(.vertical)を適用してあげます。そうするとプレビュー上は隙間がありますが、実機で動かすと確かに上下の隙間がなくなります。

  • 確認結果

右側のスクリーンショット、ステータスバーのところまで暗めの灰色になってるのが見えるでしょうか。

3. TabBarに対応させる

TabBarが絡んでくるとちょっとまた調整が必要になります。試しにそのままTabBarで囲い込んでみます。

MyNavigationView.swift
struct MyNavigationView: View {
    var body: some View {
        /*NavigationViewControllerRepresentation()*/
        TabView {
            NavigationViewControllerRepresentation().tabItem {
                Image(systemName: "1.circle")
                Text("tab1").tag(0)
            }
            Text("tab2").tabItem {
                Image(systemName: "2.circle")
                Text("tab2").tag(1)
            }
        }
    }
}


ちょっと上下にTabBarとNavigationBarが食い込んでますね。これはどういわけか.topで直ります。

SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
        // let contentView = MyNavigationView().edgesIgnoringSafeArea(.vertical)
        let contentView = MyNavigationView().edgesIgnoringSafeArea(.top) 

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }


...やった、、、のか?と思いましたが右側のスクリーンショットを見てください。ステータスバーのところが暗めの灰色ではなく、黒色になっています。そこで、また調整をします。

MyNavigationView.swift
struct MyNavigationView: View {
    var body: some View {
        /*NavigationViewControllerRepresentation()*/

        TabView {
            NavigationViewControllerRepresentation().tabItem {
                Image(systemName: "1.circle")
                Text("tab1").tag(0)
            }
            .edgesIgnoringSafeArea(.vertical) // 追加
            Text("tab2").tabItem {
                Image(systemName: "2.circle")
                Text("tab2").tag(1)
            }
        }
    }
}

やりましたね。

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