exyte / Macaw

Powerful and easy-to-use vector graphics Swift library with SVG support
MIT License
6.01k stars 553 forks source link

Animating fillVar has wrong behaviour with Auto Layout #708

Open qe0 opened 4 years ago

qe0 commented 4 years ago

Recently I faced with this issue.

public func animate<T>(from:,to:during:,delay:) where T:Macaw.Fill animates both size and color when should animates only color (fillVar). And size is animating to original svg size and then gets back immediately (check gif below).

Here's bounds.

print(icon.bounds) // (0.0, 0.0, 100.0, 100.0)
print(icon.node.bounds!) (x: 0.000000, y: 0.000000, w: 24.000000, h: 24.000000)

As you see icon's bounds is actual for Auto Layout but node's bounds are the same as source svg has.

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" d="..."/>
<path d="..."/>
</svg>

How I layout icon:

icon.contentMode = .scaleAspectFit
view.addSubview(icon)
icon.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    icon.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
    icon.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 80),
    icon.heightAnchor.constraint(equalToConstant: 100),
    icon.widthAnchor.constraint(equalToConstant: 100)
])

How I update icon's color:

extension MacawView {
    func updateShapeColor(val: Int) {
        updateShape(node: self.node) { shape in
            let animation = shape.fillVar.animation(to: Color(val: val))
            animation.play()
        }
    }

    func updateShape(node: Node, closure: (Shape) -> Void) {
        if let group = node as? Group {
            group.contents.forEach { updateShape(node: $0, closure: closure) }
        } else if let shape = node as? Shape {
            closure(shape)
        }
    }
}

gif

My guesses are:

qe0 commented 4 years ago

692 Oops.

qe0 commented 4 years ago

According to #692 and #694

This pr actually fixes uneccessary scale animation but breaks layout at right start. So I did example reproducing this bug.

When you change fillVar inside of viewDidLoad() without delay, it changes color with animation but saving original size from SVG code. The next updates (after a while) go well, but with a quick blink to actual size which should be at the very beginning.

Code reproducing this bug:

import UIKit
import Macaw

class ViewController: UIViewController {
    let icon = MacawView(node: try! SVGParser.parse(resource: "icon"), frame: .zero)

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        icon.contentMode = .scaleAspectFit

        view.addSubview(icon)
        icon.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            icon.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 120),
            icon.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40),
            icon.widthAnchor.constraint(equalToConstant: 120),
            icon.heightAnchor.constraint(equalToConstant: 120)
        ])

        // Wrong! Updates color with not actual size (120x120).
        icon.updateShapeColor(val: 0xff0000)

        // Good! Updates color with actual size (120x120),
        // but immediately changes icon size.
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.icon.updateShapeColor(val: 0x0000ff)
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.icon.updateShapeColor(val: 0x000000)
        }
    }
}

extension MacawView {
    func updateShapeColor(val: Int) {
        updateShape(node: self.node) { shape in
            shape.fillVar.animate(from: nil, to: Color(val: val), during: 1, delay: 0.0)
        }
    }

    func updateShape(node: Node, closure: (Shape) -> Void) {
        if let group = node as? Group {
            group.contents.forEach { updateShape(node: $0, closure: closure) }
        } else if let shape = node as? Shape {
            closure(shape)
        }
    }
}
Example SVG ``` ```

1234

qe0 commented 4 years ago

P.s. temporary workaround for this: For initial setup: icon.updateShapeColor(val: 0x000000, animated: false) For following updates: icon.updateShapeColor(val: 0x000000, animated: true)

    func updateShapeColor(val: Int, animated: Bool = true) {
        if animated {
            updateShape(node: self.node) { shape in
                shape.fillVar.animate(from: nil, to: Color(val: val), during: 1, delay: 0.0)
            }
        } else {
            updateShape(node: self.node) { shape in
                shape.fill = Color(val: val)
            }
        }
    }
qe0 commented 3 years ago

sigh 424

qe0 commented 3 years ago

once in a far, distant repository... Untitled-1

qe0 commented 3 years ago

...little pepe faced a problem that was scarier than he thought... Frame 1

nikita-afonasov commented 3 years ago

Hi! I was tempted to wait until the issues anniversary, as it turns out this is here for almost a year. I have some news, but most of them are bad I think 1) We didn't reply to you earlier, and for that I'm sorry. 2) I spoke to the maintainers of the project, and they acknowledge the issue, but it seems it's something that can't be fixed that easily. Also, all of them have been busy in outstaffing roles, and those understandably take precedence over something we do for fun. 3) We've been somewhat lackluster in supporting Macaw ever since Apple released SwiftUI and basically made our project obsolete (except for the SVG rendering part).

The bottom line is that we plan to fix this, but I can't promise when. At least we got some spicy memes out of it.

qe0 commented 3 years ago

@nikita-afonasov, Thanks for the reply and glad to hear it. I hope some sort of fix will be released someday. But for now, I will sink (unexpected Combine reference, wow) into the abyss of oblivion until something happens that will make me post pepes again.