简体   繁体   中英

How to make an extension for multiple types in Swift 5

I have the following extension which I want to make usable for both an UITextView and an UILabel.

extension UITextView {
    func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5) {
        text = ""
        var writingTask: DispatchWorkItem?
        writingTask = DispatchWorkItem { [weak weakSelf = self] in
            for character in typedText {
                DispatchQueue.main.async {
                    weakSelf?.text!.append(character)
                }
                Thread.sleep(forTimeInterval: characterDelay/100)
            }
        }

        if let task = writingTask {
            let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
            queue.asyncAfter(deadline: .now() + 0.05, execute: task)
        }
    }
}

I tried to set the type to UIView found here: Stack Overflow: Single extension for UITextView and UITextField in Swift

extension UIView  {
    func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5) {
        if self is UILabel || self is UITextView {
            text = ""
            var writingTask: DispatchWorkItem?
            writingTask = DispatchWorkItem { [weak weakSelf = self] in
                for character in typedText {
                    DispatchQueue.main.async {
                        weakSelf?.text!.append(character)
                    }
                    Thread.sleep(forTimeInterval: characterDelay/100)
                }
            }

            if let task = writingTask {
                let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
                queue.asyncAfter(deadline: .now() + 0.05, execute: task)
            }
        }
    }
}

That of course doesn't work because UIView has no text property so I get the following error:

Use of unresolved identifier 'text'

How can I solve this?

That of course doesn't work because UIView has no text property

This is exactly the piece you want to think about. What do you need in order to write this method? Two things: it needs a text property. And it needs to be a class type (because you use a weak modifier on it). So say that.

protocol TypeAnimated: AnyObject {
    var text: String? { get set }
}

Except that for historical reasons, UITextView has a String! while UILabel has a String? . That's very frustrating, but we can bridge the two with a new property:

protocol TypeAnimated: AnyObject {
    var animatedText: String { get set }
}

Now, given that protocol, you can write you method:

extension TypeAnimated  {
    func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5) {
        animatedText = ""
        var writingTask: DispatchWorkItem?
        writingTask = DispatchWorkItem { [weak weakSelf = self] in
            for character in typedText {
                DispatchQueue.main.async {
                    weakSelf?.animatedText!.append(character)
                }
                Thread.sleep(forTimeInterval: characterDelay/100)
            }
        }

        if let task = writingTask {
            let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
            queue.asyncAfter(deadline: .now() + 0.05, execute: task)
        }
    }
}

Now, you just need to label any types you want to conform to this protocol, and they'll get the extension.

extension UILabel: TypeAnimated {
    var animatedText: String {
        get { return text ?? "" }
        set { text = newValue }
    }
}

extension UITextView: TypeAnimated {
    var animatedText: String {
        get { return text ?? "" }
        set { text = newValue }
    }
}

As a side note, generating a new queue every time this is executed is almost certainly not what you mean. You should probably just set this up as a series of asyncAfter calls to the main queue, or use a Timer, or call asyncAfter inside the asyncAfter block. But none of this really impacts your question.

I haven't tested this, but this is how I would probably approach the problem:

extension TypeAnimated  {
    func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5/100) {
        func addNextCharacter(from string: Substring) {
            DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay) { [weak self] in
                if let self = self, let nextChar = string.first {
                    self.animatedText.append(nextChar)
                    addNextCharacter(from: string.dropFirst())
                }
            }

        }

        animatedText = ""
        addNextCharacter(from: typedText[...])
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM