質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.11%

view内にランダムに円をフェードインアウトで出現させたい

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 97

okkyu

score 17

view内で円を交互にランダムに繰り返し出現させたいのですが方法がわからず困っております。
下記のコードでfor文を使ってループさせてみたのですが、10回分が同時に出現しただけで思った通りの結果になりませんでした。
何か良い方法はないでしょうか?教えていただけましたら幸いです。

コード

class ViewController: UIViewController {

    func makeImage(width w: CGFloat,height h: CGFloat) -> UIImage{
        let size = CGSize(width: w, height: h)
        UIGraphicsBeginImageContextWithOptions(size, false, 1.0) //size、opaques、scale
        let context = UIGraphicsGetCurrentContext()
        let drawRect = CGRect(x: 0, y: 0, width: w, height: h)
        let drawPath = UIBezierPath(roundedRect: drawRect, cornerRadius: 100)
        context?.setFillColor(red: 0, green: 1.0, blue: 1.0, alpha: 1.0)
        drawPath.fill()
        context?.setStrokeColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
        drawPath.stroke()
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image!
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let c: CGFloat = 100.0
        let fw = view.frame.width
        let fh = view.frame.height

     for _ in 0..<10{
         rdmImage(viewFrameWidth: fw, viewFrameHeight: fh, circleSize: c)
        }
    }

    func rdmImage(viewFrameWidth w: CGFloat,viewFrameHeight h: CGFloat,circleSize c: CGFloat){
        var rdmWid = CGFloat(arc4random_uniform(UInt32(w)))
        var rdmHei = CGFloat(arc4random_uniform(UInt32(h)))

        if rdmWid<c/2{
            rdmWid = c/2
        }else if rdmWid>w-c/2{
            rdmWid = w-c/2
        }
        if rdmHei<c/2{
            rdmHei = c/2
        }else if rdmHei>h-c/2{
            rdmHei = h-c/2
        }

        let circleImage = makeImage(width: c, height: c)
        let circleView = UIImageView(image: circleImage)
        circleView.center = CGPoint(x: rdmWid, y: rdmHei)
        circleView.alpha = 0.0
        self.view.addSubview(circleView)

        UIView.animate(
            withDuration: 2.0,
            delay: 0,
            options: [.curveEaseInOut],
            animations: {circleView.alpha = 1.0},
            completion: {(finished: Bool) in
                self.fadeOutRemove(circleView)
        })
    }

    func fadeOutRemove(_ view: UIView){
        UIView.animate(withDuration: 2.0, delay: 0, options: UIView.AnimationOptions(), animations: {
            view.alpha = 0.0
        }, completion: {(finished: Bool) in
            view.removeFromSuperview()
        })
    }   
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

for i in 0..<10 {
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.5*Double(i)) {
        self.rdmImage(viewFrameWidth: fw, viewFrameHeight: fh, circleSize: c)
        }
    }

  
こんなんじゃダメですかね・・

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/01 05:32

    シンプルでいいと思います。なぜか2回書いてあるけど。

    キャンセル

  • 2020/07/01 09:22

    ほんとだ、すいません、修正します。

    キャンセル

  • 2020/07/01 13:26

    ご回答ありがとうございます。参考にさせていただきます。

    キャンセル

checkベストアンサー

+1

asyncAfterは最初に思いついたので、ちょっと別解を考えてみました。

UIView.animate() の completion (つまり、クロージャ)に終了後の動作を記述するのはもちろんアリなのですが、それを次々と記述していくと可読性が落ちてしまいそうなので、Core Animation の別の手法に書き換えてみました。

ここでは、DispatchQueueに入れるのではなく、CATransaction.setCompletionBlock で再帰的に rdmImage() を呼び出しています。

また、これは完全に先読みなのですが、もしアニメーションを途中で中断したくなったときに、任意のタイミングで中断できるようにもしてみました。

下記の実装ではボタンを押したタイミングでアニメーションを中断しています。

import UIKit

class ViewController: UIViewController {
    // MARK: アニメーションのレイヤ
    var animationLayer: CALayer?

    // 中略

    override func viewDidLoad() {
        super.viewDidLoad()
        // 中略

        // MARK: 再帰呼び出しするようにメソッドを書き換え。ここでは10回呼び出し
        recursiveRdmImage(viewFrameWidth: fw, viewFrameHeight: fh, circleSize: c, repeats: 10)
    }

    func recursiveRdmImage(viewFrameWidth w: CGFloat,viewFrameHeight h: CGFloat,circleSize c: CGFloat, repeats: Int) {

        var rdmWid = CGFloat.random(in: 0...w)
        var rdmHei = CGFloat.random(in: 0...h)


        if rdmWid<c/2{
            rdmWid = c/2
        }else if rdmWid>w-c/2{
            rdmWid = w-c/2
        }
        if rdmHei<c/2{
            rdmHei = c/2
        }else if rdmHei>h-c/2{
            rdmHei = h-c/2
        }

        let circleImage = makeImage(width: c, height: c)
        let circleView = UIImageView(image: circleImage)
        circleView.center = CGPoint(x: rdmWid, y: rdmHei)
        circleView.alpha = 0.0
        self.view.addSubview(circleView)

        // MARK: トランザクションとして一括登録
        CATransaction.begin()

        // MARK: トランザクション終了時の処理
        CATransaction.setCompletionBlock {
            circleView.removeFromSuperview()

            if repeats > 0 && self.animationLayer != nil {
                // MARK: 繰り返しが残っていて、かつanimationLayer != nil の場合は再帰呼び出し
                self.recursiveRdmImage(viewFrameWidth: w, viewFrameHeight: h, circleSize: c , repeats: repeats - 1)
            } else {
                // すべてのアニメーションが終わったので animationLayer を nil にする
                self.animationLayer = nil
            }
        }

        // MARK: 4秒間のアニメーションを frameAnimation としてまとめて登録する
        // layer には alpha というプロパティはなく、かわりに opacity を使う
        let animation = CAKeyframeAnimation(keyPath: "opacity")
        animation.duration = 4.0
        animation.keyTimes = [0.0, 0.5, 1.0]
        animation.values = [0.0, 1.0, 0.0]
        // MARK: アニメーションを登録する。
        // forkey: は必須ではないが、あとで削除するときにキーが必要なので登録
        circleView.layer.add(animation, forKey: "rdmImage")

        // MARK: アニメーションを止めるときに使う
        animationLayer = circleView.layer
        CATransaction.commit()
    }

    // MARK: 止めるボタンを押した時の処理
    @IBAction func stopButton(_ sender: Any) {
        if animationLayer == nil {
            print("no animations")
            return
        }

        // forkey で指定したアニメーションを止める
        animationLayer?.removeAnimation(forKey: "rdmImage")
        animationLayer = nil

    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/01 13:26

    ご回答ありがとうございます。悩んでいたことが解決でき、とても参考になりました。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.11%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る