20200626のSwiftに関する記事は6件です。

UITextDocumentProxyのsetMarkedTextを(まだ)使ってはいけない。

概要

iOSのKeyboard Extensionを自作するとき、必ず触ることになるのがUITextDocumentProxyです。

iOS13からはsetMarkedText(_:selectedRange)というメソッドが追加され、これを使うことで標準の日本語キーボードのような「入力中のテキストの背景の色が変わる」挙動が実現できるように思われます。

実際、4月に複数のキーボードで調べたところでは2つのKeyboard Extension(flickFlick SKK)がsetMarkedText()を用いていると思われる挙動を示していました。

挙動の詳細

チェックした環境はiOS 13.5.1のiPhoneSE(第1世代・第2世代)です。

ezgif-6-0ef87aa0999a.gif

少し何が起きているかわかりにくいですが、消える瞬間にテキストをタップしています。標準キーボードを利用してきたユーザが期待する動作はカーソルの移動ですが、(何故か)入力中のテキストが全く見えなくなる挙動を示します。
これはFlick SKKについても同一です。そもそもsetMarkedText(_:selectedRange)がこの挙動を示すので、当然といえば当然です。

まだ使うべきではない

入力中のテキストの背景の色が変わるのは標準キーボードで当然の動作ですし、できれば実装したほうがユーザフレンドリーな動作です。が、それによってできることが著しく制限されます。実際に制限されるのは

  • 入力中の(キーボードを介さない)カーソル移動
    • 入力中の(キーボードを介さない)変換範囲の指定
    • 入力中の(キーボードを介さない)誤字の修正
  • 入力中の範囲外タップによる入力終了
    • flickでは範囲外タップで入力中の文章が消滅する(仕様?)

などなどです。当然バグの温床でもあります。
さらに、この挙動を修正する方法は今のところありません。散々格闘しましたが、標準のキーボードはおそらくsetMarkedTextを使っていないんじゃないかと思います。もし詳しいことをご存知の方いらっしゃいましたら教えてください。
ただ、いろいろなKeyboard Extensionのレビューを見て見ると「入力中の文字の色が変わるようにしてくれ」という要望はかなり多いです。iOS12までは「appleに言ってくれ」が答えでしたが、iOS13では「appleに文句言ってくれ」と答えたいところでしょう。なんでappleはわざわざこんな使えないものを追加したんだ……。

参考

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

初めてのアプリ SwiftUI自動で消えるAnimationを実装

IMG_6413.GIF

アプリの中でよくあるこのような表示ですが、
私のアプリの中にも実装してみたいと思っていました。
結構時間がかかりました。

最初は、Buttonの後ろに.Alertで入れてみましたが、
1秒後に自動に消える機能がなく、AlertのPop画面でユーザに「OK」をクリックしてもらって
消えるしかありません。

そして、プログラミング動画を見てZStackの意味を勘違いしていたのに気づいて、
ZStack Viewで実現できるかもしれないと思って、手を動かして一日くらいやっとう実現できました。

ZStackViewで「画像/文字」のViewを出すのは簡単ですが、
消えてもらうのは難しい、Delay,Disappear,Disable,Removeなど色々KeyWordで検索しても。。。
Animationの中にあるtimingCurveは使えると思っていましたが、勘違いでした。

プログラミング動画(UIKitのAnimationの使い方)を見て、ViewのX/Yの値を変えることによって、
Viewを画面のBottomのさらに下で表示させるか、画面の中で表示させると変えられます。
それを真似してSwift UIの中のOffset(x:,y:)で実装できました!

ソースコードはこんな感じです。
withAnimation{click.wrappedValue = false}は、
withAnimation{click.wrappedValue.toggle()}でもいいですが、
実際に実装する際、他のButtonとViewと支障が出たりするので、
そこら辺をもうちょっと工夫して修正する必要があります。

starHeadView
func alertMessage(click:Binding<Bool>,imageYes:String,imageNo:String,
                  textYes:String,textNo:String,colorYes:Color,colorNo:Color)
                  -> some View {
        HStack{
            if userData.userStarProfiles[starProfileIndex].favorite {
                Image(systemName:imageYes)
                .frame(width: 30, height: 30)
                .foregroundColor(colorYes)
                Text(textYes).fontWeight(.light)
                .onDisappear(perform: {
                    withAnimation{
                        click.wrappedValue = false
                    }
                })
            }else if !userData.userStarProfiles[starProfileIndex].favorite {
                Image(systemName:imageNo)
                .frame(width: 30, height: 30)
                .foregroundColor(colorNo)
                Text(textNo).fontWeight(.light)
                .onDisappear(perform: {
                    withAnimation{
                        click.wrappedValue = false
                    }
                })
            }
        }
        .animation(Animation.easeOut(duration: 1.5)).offset(x:110,y: click.wrappedValue ? 50 : -300)
    }

そして、ZStack Viewの中で上記関数を呼び出す。

starHeadView
alertMessage(click: self.$favoriteClick, imageYes: "heart.fill", imageNo:"heart", textYes: "好き!", textNo: "", colorYes: .pink, colorNo: .gray)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】 Color Setを使うときのちょっとした注意

iOS11(Xcode9)からImage Setのように、色を定義することの出来るColor Setが実装され、Storyboard上での色管理の煩わしさから開放されました。
もちろん、コード上からも使うことが出来てとても便利なのですが、ちょっとした注意点があったので、備忘録として残しておこうかなと思いました。

あるプロジェクトで遭遇したことなのですが、Storyboard上で定義したLabelで、コード上からAttributedTextで色を変更しようとしたときのことです。
iOS13で起動したときは確かに色が変わっていたはずなのに、iOS11や12の端末で起動してみると、

色が変わらない

という現象が発生しました。
Great Scott!(BTTF大好き)と何度もつぶやきながら調べましたが、どうやら原因は「Storyboard上でColor Setを使用しているから」のようです。

特にサイトを調べたりしたわけではなく、Storyboardをいじっていたら発見したことなので本当かは定かではありませんが、少なくともStoryboard上で該当LabelのtextColorを、Xcodeで最初から定義されている色(black, white, default, etc...)に戻して再度アプリを実行すると、

ちゃんと色が変わってる!

という結果が得られました。

まとめ

iOS11や12を対象とするプロジェクトでColor Setを使う場合、Storyboardで設定した色を変更するときは注意が必要です。

意外と見落としがちなところかもしれないので、実際にそういったアプリを制作されている方は少しだけ注意したほうがいいかもしれません

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

Akashic Engine にて作成したニコ生向けゲームをiOSアプリ用に移植する方法

はじめに

本記事では、実質ニコ生ゲーム用のゲームエンジンであるAkashic Engineを使用した
ゲームをiOSアプリに移植したときの備忘録です。
HTML5で作られた他のエンジン製のゲームでも応用できるかと思います。
なお、Akashic Engineのマルチプレイ機能を用いたゲームは対象外です。

※当記事のやり方では審査を通らない可能性が高いです。ご了承ください

AkashicEngine 2.7
XCode 11.5

手順

1.Akashic Engineのゲームをスタンドアローン形式で出力。

https://akashic-games.github.io/tutorial/v2/7-export.html
akashic export html --magnify --output ../mygame

2.XCodeで新規プロジェクト作成

File→New→Project
iOSタグ→Single View App
※User Interface を Storyboardとすること

3.1で作成したファイルをプロジェクト直下に配置する

1で作成したmygameフォルダをそのままドラッグで良い

4.WKWebViewを配置し、ローカルのhtmlを表示する

ViewController.swift
import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {

    var webView: WKWebView!

    override func loadView() {


        let webConfiguration = WKWebViewConfiguration()

        //WKWebView に Configuration を引き渡し initialize
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        //WKUIDelegate の移譲先として self を登録
        webView.uiDelegate = self
        //WKNavigationDelegate の移譲先として self を登録
        webView.navigationDelegate = self
        //view に webView を割り当て
        view = webView

    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // 読み込み開始
        if let html = Bundle.main.path(forResource: "mygame/index", ofType: "html") {
            let url = URL(fileURLWithPath: html)
            let request = URLRequest(url: url)
            webView.load(request)
        }
    }


}

5.横画面表示のみとする

TARGETS→General→Deployment Info
のDeviceOrienttatitonのチェックボックスを
Landscape LeftとLandscape Rightのみとする

これで大体完成です。
アイコン等を設定すればストアに申請できます。
ただし、通りません。

Guideline 4.2 - Design - Minimum Functionality
となってしまいます。
何か機能を追加すれば通るかもしれません。

以上。

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

swift5でdictionaryをソートする

初めに

今回はdictionary型の変数をソートする方法を展開します

dictionaryについて

此奴はハッシュテーブルな為、素早くキー検索ができるといういい点がありますが、格納順序は保証してくれるものではありません。
いいたいことは以下のことを指しています。
何もしないで抽出

test.swift
let testDict:[String:String] = ["01":"msTest","02":"mrTest","03":"Test"]
for (key, val) in testDict {
    println("key:\(key)\n value:\(val)")
}

出力結果

key:02
value:mrTest
key:01
value:msTest
key:03
value:Test

こんな風にぐちゃぐちゃに辞書に格納されてしまいます。

今回の議題の解決策

順番を保証してくれるデータ型などはありますが、抽出の方法が違かったりと多々問題が出てくるので、dictionaryを手っ取り早くソートして順番通り表示します

test.swift
let testDict:[String:String] = ["01":"msTest","02":"mrTest","03":"Test"]
//昇順でソート
let sortData = testDict.sorted(by: {$0.0 < $1.0 })
for (key, val) in sortData {
    println("key:\(key)\n value:\(val)")
}

出力結果

key:01
value:msTest
key:02
value:mrTest
key:03
value:Test

終わり

このように一文追加することでソートされます。
昇順で今回は例に出しましたが、もちろん降順でもソート可能です!
言語が保証してくれない物を無理やりソートするより、保証している物を使った方が楽ですが、dictionaryは扱いやすいし早いしなかなか捨てがたい物ではありますよね!
この記事がどなたかの助けになることを願っております。

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

【プログラミング初心者】Swift基礎~デリゲート~

はじめに

今回はデリゲートと呼ばれるプログラミングの手法について紹介します。
プロトコルを使った実装になるのでプロトコルがわからない方はこちらをまず参照してください。

デリゲートはiOSアプリでよく出てきます。
例えばUITableViewUICollectionViewなどです。
よく出るがゆえに避けては通れないので内部ではどんな実装になっているのか想像できるようにはしておきたいですね。

デリゲート(Delegate)

デリゲートとは

デリゲートはSwiftの文法ではなく、プログラミングの手法の1つです。
つまり、こんな感じでクラスを作ると便利ですよというようなクラス作成のパターンです。
このようなパターンのことをデザインパターンなどと言ったりします。

デリゲート(Delegate)には「委譲する」、「任せる」というがあります。
あるオブジェクトの処理を他のオブジェクトに任せる目的で使います。

デリゲートには「処理を任せる」オブジェクトと「処理を任される」オブジェクトが出てきます。

「処理を任せる」オブジェクトにボタンがあったとします。
そのボタンをタップしたとき何らかの処理を行ないますが、その具体的な処理は「処理を任される」オブジェクトに任せるます。
「処理を任される」オブジェクトから見るとボタンが押されたとき「処理を任せる」オブジェクトからボタンが押されたという通知が来るようなイメージです。

言葉で説明するとこんな感じの手法ですが、実際の実装方法を見たほうがわかるかもしれません。
とはいっても以前説明したプロトコルを使った実装例とあまり実装の違いはありません。

デリゲートの実装

あるボタンクラスがあり、そのボタンが押されたら何かしらの処理をするというように実装していきます。

デリゲートを定義する

デリゲートはプロトコルで定義します。

ButtonDelegate.swift
protocol ButtonDelegate {
    func didTapBotton()
}

ボタンオブジェクトがタップされたらdidTapBotton()が呼び出されるという想定です。
デリゲートには委譲する処理を定義していきます。

「処理を任せる」オブジェクトを作成する

今回の場合ボタンクラスが「処理を任せる」オブジェクトになります。
以下のような実装となります。

Button.swift
class Button {
    var delegate: ButtonDelegate? = nil

    func onTapButton() {
        print("ボタンがタップされました")
        print("処理を委譲します")

        delegate?.didTapBotton()
    }
}

ボタンが押されるとonTapButton()が実行されるとします。
onTapButton()で何らかの処理を行った後ButtonDelegatedidTapBotton()を呼び出します。
これで委譲する準備は完了です。

ButtonDelegateはプロトコルなので具体的なクラスは気にしません。
とりあえずボタンが押されたらdidTapBotton()を呼び出してあげるから、あとは好きな処理をしといてー
というようなイメージです。

「処理を任される」オブジェクトを実装する

「処理を任される」オブジェクトは画面クラスやその他テキトーなクラスなど何でも構いません。
ButtonDelegateを継承しているだけでいいのです。

Hoge.swift
class Hoge: ButtonDelegate {
    func didTapBotton() {
        print("Hogeに処理が委譲されました")
    }
}

委譲されるクラスの実装はこれだけです。
ボタンがタップされたらdidTapBotton()が呼び出されるのでここに好きな処理を書きます。

実行する

これでデリゲートの実装ができました。
それでは実際に使ってみましょう。

let button = Button()
let hoge = Hoge()

button.delegate = hoge // delegateに委譲先のオブジェクトをセットする

button.onTapButton() // ボタンがタップされたと想定
実行結果
ボタンがタップされました
処理を委譲します
Hogeに処理が委譲されました

button.delegate = hogeで委譲元オブジェクトのデリゲートに委譲先オブジェクトを設定し紐付けしています。

このようにしてデリゲートで処理を委譲します。

最後に

今回はデリゲートの実装方法を紹介しました。
基本的にプロトコルの実装とあまり変わらないのではないでしょうか?

冒頭で言ったようにiOSではUICollectionViewなどでよくデリゲートが使われています。
(DataSourceもありますがこれもデリゲートと似たようなものです)

UICollectionViewのデリゲートはよく画面クラスで継承しますが、内部ではこのようにして処理を通知するような実装がされています。

自分で実装するのはまだ難しいかも知れませんが内部の処理を想像できる程度に理解できていればいいかなと思います。

今回の内容は以上です。
本記事とは別でプログラミング未経験からiOSアプリ開発が行えるようになることを目的とした記事を連載しています。
連載は以下にまとめていますのでそちらも是非もご覧ください。
http://naoyalog.com/

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