Input-mask-ios: рд╕рдВрдкрд╛рджрди рдХреЗ рдмрд╛рдж рдЧрд▓рдд рдореБрдЦреМрдЯрд╛ рд╕рдВрд▓рдЧреНрди

рдХреЛ рдирд┐рд░реНрдорд┐рдд 25 рдЬреБрд▓ре░ 2018  ┬╖  11рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: RedMadRobot/input-mask-ios

рдореЗрд░реЗ рдкрд╛рд╕ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдорд╛рд╕реНрдХ рд╕реЗрдЯ рд╣реИрдВ:
self.login.affineFormats = [ "{+7} ([000]) [000] [00] [00]", "{8} ([000]) [000]-[00]-[00]" ]

рдЬрдм рдореИрдВ +7 рдорд╛рд╕реНрдХ рдХреЗ рд╕рд╛рде рдПрдХ рдлреЛрди рдирдВрдмрд░ рдЯрд╛рдЗрдк рдХрд░рддрд╛ рд╣реВрдВ рдФрд░ рдлрд┐рд░ ([000]) рдХреЗ рднреАрддрд░ рдорд╛рди рдХреЛ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░рддрд╛ рд╣реВрдВ рддреЛ рдорд╛рд╕реНрдХ +7 рдХреЗ рдмрдЬрд╛рдп 8 рдкрд░ рд╕реНрд╡рд┐рдЪ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдлрд┐рд░ 7 рдХреЛ рдЕрдВрджрд░ рд░рдЦ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ ([000])

рд╕рдмрд╕реЗ рдЙрдкрдпреЛрдЧреА рдЯрд┐рдкреНрдкрдгреА

рд╣рд╛рдБ рдЗрд╕реЗ рдареАрдХ рдХрд░ рджрд┐рдпрд╛, рдзрдиреНрдпрд╡рд╛рдж!

рд╕рднреА 11 рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

рд╣реЛ рд╕рдХрддрд╛ рд╣реИ рдХрд┐ рдХрд┐рд╕реА рднреА рдорд╛рд╕реНрдХ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж рдЯреЗрдХреНрд╕реНрдЯрдлрд┐рд▓реНрдб рдореЗрдВ рдорд╛рд╕реНрдХ рдХреЛ "рд▓реЙрдХ" рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рд╣реЛ?

рд╣рд╛рдп @MrJox , рдЖрдкрдХреА рд░рд┐рдкреЛрд░реНрдЯ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред
рдЬрдм рдпрд╣ рддреНрд░реБрдЯрд┐ рд╣реЛрддреА рд╣реИ рддреЛ рдХреНрдпрд╛ рдЖрдк рдХреГрдкрдпрд╛ рдЕрдкрдиреЗ рдЯреЗрдХреНрд╕реНрдЯ рдлрд╝реАрд▓реНрдб рдХреЗ рдЕрдВрджрд░ рдЯреЗрдХреНрд╕реНрдЯ рдХреА рд╕рдЯреАрдХ рд╕реНрдерд┐рддрд┐ рдкреНрд░рджрд╛рди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ?

рдореИрдВ рдЖрдЬ рдмрд╛рдж рдореЗрдВ рдЗрд╕рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛ рд╕рдХреВрдВрдЧрд╛ рдФрд░ рдлрд┐рд░ рд╕рдорд╛рдзрд╛рди рдореЗрдВ рдЖрдкрдХреА рд╕рд╣рд╛рдпрддрд╛ рдХрд░ рд╕рдХреВрдВрдЧрд╛ред

рдирд┐рд╢реНрдЪрд┐рдд рдирд╣реАрдВ рд╣реИ рдХрд┐ рд░рд╛рдЬреНрдпреЛрдВ рд╕реЗ рдЖрдкрдХрд╛ рдХреНрдпрд╛ рдорддрд▓рдм рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣рд╛рдБ рдХреНрдпрд╛ рд╣реЛрддрд╛ рд╣реИ:

рдореЗрд░реЗ рдкрд╛рд╕ рдХреИрд╢ рд╕реЗ рдкрдврд╝рд╛ рдЧрдпрд╛ рдПрдХ рдлреЛрди рдирдВрдмрд░ рд╣реИ рдФрд░ рдЯреЗрдХреНрд╕реНрдЯрдлрд┐рд▓реНрдб рдореЗрдВ рдбрд╛рд▓рд╛ рдЧрдпрд╛ рд╣реИ (рдпрд╣ рдПрдХ рдХрд╕реНрдЯрдо рдЬреЗрд╡реАрдлреНрд▓реЛрдЯ рдЯреЗрдХреНрд╕реНрдЯрдлрд┐рд▓реНрдб рд╣реИ)ред
рдлреЛрди рдирдВрдмрд░ +7 (926) 000-00-00 рд╣реИ рдФрд░ рдлрд┐рд░ рдореИрдВ '2' рдХреЗ рдмрд╛рдж рдкреЙрдЗрдВрдЯрд░ рд╕реЗрдЯ рдХрд░рддрд╛ рд╣реВрдВ рдФрд░ рдЗрд╕ рдирдВрдмрд░ рдХреЛ рд╣рдЯрд╛ рджреЗрддрд╛ рд╣реВрдВред рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рдореЗрд░реА рдЯреЗрдХреНрд╕реНрдЯ рд╡реНрдпреВ рд╕рд╛рдордЧреНрд░реА 8 (796) 000-00-00 рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рд╣реЛ рдЬрд╛рддреА рд╣реИред

рддреЛ рдХрд┐рд╕реА рдХрд╛рд░рдг рд╕реЗ рдпрд╣ + рд╣рдЯрд╛ рджреЗрддрд╛ рд╣реИ рдФрд░ 7 рдХреЛ рдорд╛рд╕реНрдХ рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВ рдмрд▓реНрдХрд┐ рдлреЛрди рдирдВрдмрд░ рдХреЗ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╣рд┐рд╕реНрд╕реЗ рдХреЗ рд░реВрдк рдореЗрдВ рдорд╛рдирддрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдмреНрд░реИрдХреЗрдЯ рдореЗрдВ рд░рдЦрддрд╛ рд╣реИ рдФрд░ рдлрд┐рд░ 8 рдорд╛рд╕реНрдХ рдЬреЛрдбрд╝рддрд╛ рд╣реИред

рд╣рд╛рдБ, рдЖрдкрдХрд╛ рд╡рд┐рд╡рд░рдг рдореЗрд░реЗ рд▓рд┐рдП рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рдзрдиреНрдпрд╡рд╛рдж!
рдмрдиреЗ рд░рд╣реЗрдВред

@MrJox рдЬрдм рдореИрдВ рдЕрдкрдиреЗ рдкрд┐рдЫрд▓реЗ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЕрдкрдбреЗрдЯ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдХреБрдЫ рд╕реБрдзрд╛рд░реЛрдВ рдФрд░ рд╕реБрдзрд╛рд░реЛрдВ рдкрд░ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реВрдВ, рддреЛ рдореИрдВрдиреЗ рдЖрдкрдХреА рд╕рдорд╕реНрдпрд╛ рдХрд╛ рддреНрд╡рд░рд┐рдд рд╕рдорд╛рдзрд╛рди рдПрдХ рд╕рд╛рде рд░рдЦрд╛ рд╣реИред

рдкреНрд░рднрд╛рд╡реА рд░реВрдк рд╕реЗ, рдпрд╣ рдПрдХ рдХрд╕реНрдЯрдо рдПрдлрд╝рд┐рдирд┐рдЯреА рдЧрдгрдирд╛ рдкрджреНрдзрддрд┐ рдХреЗ рд╕рд╛рде PolyMaskTextFieldDelegate рд╕рдВрд╢реЛрдзрди рд╣реИред рдореБрдЦреМрдЯрд╛ рдХреЗ рд╕рд╛рде рдПрдХ рд╕рдордЧреНрд░ рдкрд╛рда рдЖрддреНрдореАрдпрддрд╛ рдХреЗ рдмрдЬрд╛рдп, рдпрд╣ рд╡рд┐рдзрд┐ рдЙрдкрд╕рд░реНрдЧреЛрдВ рдХреЗ рдЪреМрд░рд╛рд╣реЗ рдХреА рд▓рдВрдмрд╛рдИ, рд╡рд░реНрдгреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдХреА рддреБрд▓рдирд╛ рдХрд░рддреА рд╣реИред

рдпрд╣ рдХреЛрдб рдпрд╛ рдЗрд╕рдХрд╛ рд╕рдВрд╢реЛрдзрд┐рдд рд╕рдВрд╕реНрдХрд░рдг рдЕрдВрддрддрдГ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕реНрд░реЛрддреЛрдВ рдХреЗ рд▓рд┐рдП рдЕрдкрдирд╛ рд░рд╛рд╕реНрддрд╛ рдЦреЛрдЬ рд╕рдХрддрд╛ рд╣реИред

import Foundation
import UIKit

import InputMask

<strong i="10">@IBDesignable</strong>
class PrefixAffinityMaskedTextFieldDelegate: MaskedTextFieldDelegate {
    public var affineFormats: [String]

    public init(primaryFormat: String, affineFormats: [String]) {
        self.affineFormats = affineFormats
        super.init(format: primaryFormat)
    }

    public override init(format: String) {
        self.affineFormats = []
        super.init(format: format)
    }

    override open func put(text: String, into field: UITextField) {
        let mask: Mask = pickMask(
            forText: text,
            caretPosition: text.endIndex,
            autocomplete: autocomplete
        )

        let result: Mask.Result = mask.apply(
            toText: CaretString(
                string: text,
                caretPosition: text.endIndex
            ),
            autocomplete: autocomplete
        )

        field.text = result.formattedText.string
        field.caretPosition = result.formattedText.string.distance(
            from: result.formattedText.string.startIndex,
            to: result.formattedText.caretPosition
        )

        listener?.textField?(field, didFillMandatoryCharacters: result.complete, didExtractValue: result.extractedValue)
    }

    override open func deleteText(inRange range: NSRange, inTextInput field: UITextInput) -> Mask.Result {
        let text: String = replaceCharacters(
            inText: field.allText,
            range: range,
            withCharacters: ""
        )

        let mask: Mask = pickMask(
            forText: text,
            caretPosition: text.index(text.startIndex, offsetBy: range.location),
            autocomplete: false
        )

        let result: Mask.Result = mask.apply(
            toText: CaretString(
                string: text,
                caretPosition: text.index(text.startIndex, offsetBy: range.location)
            ),
            autocomplete: false
        )

        field.allText = result.formattedText.string
        field.caretPosition = range.location

        return result
    }

    override open func modifyText(
        inRange range: NSRange,
        inTextInput field: UITextInput,
        withText text: String
    ) -> Mask.Result {
        let updatedText: String = replaceCharacters(
            inText: field.allText,
            range: range,
            withCharacters: text
        )

        let mask: Mask = pickMask(
            forText: updatedText,
            caretPosition: updatedText.index(updatedText.startIndex, offsetBy: field.caretPosition + text.count),
            autocomplete: autocomplete
        )

        let result: Mask.Result = mask.apply(
            toText: CaretString(
                string: updatedText,
                caretPosition: updatedText.index(updatedText.startIndex, offsetBy: field.caretPosition + text.count)
            ),
            autocomplete: autocomplete
        )

        field.allText = result.formattedText.string
        field.caretPosition = result.formattedText.string.distance(
            from: result.formattedText.string.startIndex,
            to: result.formattedText.caretPosition
        )

        return result
    }

    func pickMask(forText text: String, caretPosition: String.Index, autocomplete: Bool) -> Mask {
        let primaryAffinity: Int = calculateAffinity(
            ofMask: mask,
            forText: text,
            caretPosition: caretPosition,
            autocomplete: autocomplete
        )

        var masks: [(Mask, Int)] = affineFormats.map { (affineFormat: String) -> (Mask, Int) in
            let mask:     Mask = try! Mask.getOrCreate(withFormat: affineFormat, customNotations: customNotations)
            let affinity: Int  = calculateAffinity(
                ofMask: mask,
                forText: text,
                caretPosition: caretPosition,
                autocomplete: autocomplete
            )

            return (mask, affinity)
        }

        masks.sort { (left: (Mask, Int), right: (Mask, Int)) -> Bool in
            return left.1 > right.1
        }

        var insertIndex: Int = -1

        for (index, maskAffinity) in masks.enumerated() {
            if primaryAffinity >= maskAffinity.1 {
                insertIndex = index
                break
            }
        }

        if (insertIndex >= 0) {
            masks.insert((mask, primaryAffinity), at: insertIndex)
        } else {
            masks.append((mask, primaryAffinity))
        }

        return masks.first!.0
    }

    func calculateAffinity(
        ofMask mask: Mask,
        forText text: String,
        caretPosition: String.Index,
        autocomplete: Bool
    ) -> Int {
        return mask.apply(
            toText: CaretString(
                string: text,
                caretPosition: caretPosition
            ),
            autocomplete: autocomplete
        ).formattedText.string.prefixIntersection(with: text).count
    }

    func replaceCharacters(inText text: String, range: NSRange, withCharacters newText: String) -> String {
        if 0 < range.length {
            let result = NSMutableString(string: text)
            result.replaceCharacters(in: range, with: newText)
            return result as String
        } else {
            let result = NSMutableString(string: text)
            result.insert(newText, at: range.location)
            return result as String
        }
    }

}


extension String {

    func prefixIntersection(with string: String) -> Substring {
        let lhsStartIndex = startIndex
        var lhsEndIndex = startIndex
        let rhsStartIndex = string.startIndex
        var rhsEndIndex = string.startIndex

        while (self[lhsStartIndex...lhsEndIndex] == string[rhsStartIndex...rhsEndIndex]) {
            lhsEndIndex = lhsEndIndex != endIndex ? index(after: lhsEndIndex) : endIndex
            rhsEndIndex = rhsEndIndex != string.endIndex ? string.index(after: rhsEndIndex) : endIndex

            if (lhsEndIndex == endIndex || rhsEndIndex == string.endIndex) {
                return self[lhsStartIndex..<lhsEndIndex]
            }
        }

        return self[lhsStartIndex..<lhsEndIndex]
    }

}


extension UITextInput {
    var allText: String {
        get {
            guard let all: UITextRange = allTextRange
                else { return "" }
            return self.text(in: all) ?? ""
        }

        set(newText) {
            guard let all: UITextRange = allTextRange
                else { return }
            self.replace(all, withText: newText)
        }
    }

    var caretPosition: Int {
        get {
            if let responder = self as? UIResponder {
                // Workaround for non-optional `beginningOfDocument`, which could actually be nil if field doesn't have focus
                guard responder.isFirstResponder
                    else { return allText.count }
            }

            if let range: UITextRange = selectedTextRange {
                let selectedTextLocation: UITextPosition = range.start
                return offset(from: beginningOfDocument, to: selectedTextLocation)
            } else {
                return 0
            }
        }

        set(newPosition) {
            if let responder = self as? UIResponder {
                // Workaround for non-optional `beginningOfDocument`, which could actually be nil if field doesn't have focus
                guard responder.isFirstResponder
                    else { return }
            }

            if newPosition > allText.count {
                return
            }

            let from: UITextPosition = position(from: beginningOfDocument, offset: newPosition)!
            let to:   UITextPosition = position(from: from, offset: 0)!
            selectedTextRange = textRange(from: from, to: to)
        }
    }

    var allTextRange: UITextRange? {
        return self.textRange(from: self.beginningOfDocument, to: self.endOfDocument)
    }
}

рд╣рдореНрдо, рддреЛ рдЕрдм рдореИрдВ рдпрд╣ рдХрд░рддрд╛ рд╣реВрдВ:
<strong i="6">@IBOutlet</strong> var loginListener: PrefixAffinityMaskedTextFieldDelegate!

рд▓реЗрдХрд┐рди рдпрд╣ рдЕрднреА рднреА рдХрд╛рдо рдирд╣реАрдВ рдХрд░реЗрдЧрд╛ред рдореИрдВрдиреЗ рдХреНрдпрд╛ рджреЗрдЦрд╛, рдпрджрд┐ рдЖрдк рдбреЗрд▓ рдХреБрдВрдЬреА рдХреЗ рд╕рд╛рде рдмреНрд░реИрдХреЗрдЯ рдХреЗ рднреАрддрд░ рдЕрдВрдХ рд╣рдЯрд╛рддреЗ рд╣реИрдВ, рддреЛ рдЕрдВрдХ рд╣рдЯрд╛ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдЕрдм рдпрд╣ рдмреНрд░реИрдХреЗрдЯ рдХреЗ рднреАрддрд░ рдПрдХ рдХрдо рдЕрдВрдХ рд╣реИ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдЬрдм рдореИрдВ рдмреИрдХрд╕реНрдкреЗрд╕ рдмрдЯрди рд╕реЗ рд╣рдЯрд╛рддрд╛ рд╣реВрдВ, рддреЛ рдпрд╣ рдпрд╛ рддреЛ рдмрд╛рдПрдВ рд╕реЗ рдЕрдВрдХ рдпрд╛ рджрд╛рдПрдВ рд╕реЗ рдЕрдВрдХ рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд/рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рддрд╛ рд╣реИ рдХреЛрд╖реНрдардХ рдХреЗ рдЕрдВрджрд░ рдЕрдВрдХреЛрдВ рдХреА рдорд╛рддреНрд░рд╛ рдЕрдкрд░рд┐рд╡рд░реНрддрд┐рдд рд░рд╣рддреА рд╣реИред

рдореЗрд░рд╛ рдорддрд▓рдм:
рдореВрд▓: +7 (916) 000-00-00
рдбреЗрд▓ рдХреБрдВрдЬреА рдХреЗ рд╕рд╛рде: +7 (96) 000-00-00
рдмреИрдХрд╕реНрдкреЗрд╕ рдХреБрдВрдЬреА рдХреЗ рд╕рд╛рде: 8 (796) 000-00-00

рд╕реБрдзрд╛рд░:
рдЕрдм рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЬрдм рдореИрдВ рдЯреЗрдХреНрд╕реНрдЯрдлрд┐рд▓реНрдб рдореЗрдВ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЛ рд╣рдЯрд╛рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддрд╛ рд╣реВрдВ, рддреЛ рдореЗрд░реЗ рдкрд╛рд╕ рдХреЗрд╡рд▓ '+' рд╡рд░реНрдг рд╢реЗрд╖ рд╣реЛрддрд╛ рд╣реИ рдФрд░ рдЬрдм рдореИрдВ рдЗрд╕реЗ рд╣рдЯрд╛рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддрд╛ рд╣реВрдВ рддреЛ рдореЗрд░рд╛ рдРрдк рдХреНрд░реИрд╢ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ

рдереНрд░реЗрдб 1: рдШрд╛рддрдХ рддреНрд░реБрдЯрд┐: рдПрдВрдбрдЗрдВрдбреЗрдХреНрд╕ рд╕реЗ рдЖрдЧреЗ рдирд╣реАрдВ рдмрдврд╝ рд╕рдХрддрд╛

@MrJox рдореБрдЭреЗ рдпрдХреАрди рдирд╣реАрдВ рд╣реИ рдХрд┐ рдореИрдВрдиреЗ рдЖрдкрдХреЛ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рд╕рдордЭрд╛ рд╣реИред
рдзреНрдпрд╛рди рджреЗрдВ, рдЖрдИрдУрдПрд╕ рдкрд░ рдХреЛрдИ Del рдХреБрдВрдЬреА рдирд╣реАрдВ рд╣реИ, рдХреЗрд╡рд▓ Backspace рд╣реИред рд╡рд░реНрдЪреБрдЕрд▓ рдХреАрдмреЛрд░реНрдб рдЗрдореНрдпреВрд▓реЗрд╢рди рдХреЛ рдореВрд░реНрдЦ рдордд рдмрдирдиреЗ рджреЛред

рдЕрдкрдиреА рдкрд┐рдЫрд▓реА рддреНрд░реБрдЯрд┐ рдХреЗ рд╕рдВрдмрдВрдз рдореЗрдВ, рдЖрдк рдХрд┐рдирд╛рд░реЗ рдХреЗ рдорд╛рдорд▓реЗ рдХреЗ рд▓рд┐рдП рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдкреИрдЪ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

extension String {

    func prefixIntersection(with string: String) -> Substring {
        guard !self.isEmpty && !string.isEmpty
        else { return "" }

        let lhsStartIndex = startIndex
        var lhsEndIndex = startIndex
        let rhsStartIndex = string.startIndex
        var rhsEndIndex = string.startIndex

        while (self[lhsStartIndex...lhsEndIndex] == string[rhsStartIndex...rhsEndIndex]) {
            lhsEndIndex = lhsEndIndex != endIndex ? index(after: lhsEndIndex) : endIndex
            rhsEndIndex = rhsEndIndex != string.endIndex ? string.index(after: rhsEndIndex) : endIndex

            if (lhsEndIndex == endIndex || rhsEndIndex == string.endIndex) {
                return self[lhsStartIndex..<lhsEndIndex]
            }
        }

        return self[lhsStartIndex..<lhsEndIndex]
    }

}

рд╣рд╛рдБ рдЗрд╕реЗ рдареАрдХ рдХрд░ рджрд┐рдпрд╛, рдзрдиреНрдпрд╡рд╛рдж!

@MrJox рдореИрдВ рдЗрд╕ рдореБрджреНрджреЗ рдХреЛ рдЕрдЧрд▓реЗ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЕрдкрдбреЗрдЯ рдХреЗ рд╕рд╛рде рдмрдВрдж рдХрд░ рджреВрдВрдЧрд╛ред
рдореИрдВ рдЗрд╕реЗ рд╡реИрдХрд▓реНрдкрд┐рдХ рд░рдгрдиреАрддрд┐ рдХреЗ рд░реВрдк рдореЗрдВ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕реНрд░реЛрддреЛрдВ рдореЗрдВ рдЬреЛрдбрд╝рдиреЗ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░ рд░рд╣рд╛ рд╣реВрдВред

@MrJox рдЕрдиреБрдХреВрд▓рд┐рдд рдФрд░ рд╕рд░рд▓реАрдХреГрдд рд╕рдВрд╕реНрдХрд░рдг:

extension String {

    func prefixIntersection(with string: String) -> Substring {
        var lhsIndex = startIndex
        var rhsIndex = string.startIndex

        while lhsIndex != endIndex && rhsIndex != string.endIndex {
            if self[...lhsIndex] == string[...rhsIndex] {
                lhsIndex = index(after: lhsIndex)
                rhsIndex = string.index(after: rhsIndex)
            } else {
                return self[..<lhsIndex]
            }
        }

        return self[..<lhsIndex]
    }

}

4.0.0 рдореЗрдВ рдпрд╣ рдПрдХ рд╡рд┐рд╢реЗрд╖рддрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реИред

рдХреНрдпрд╛ рдпрд╣ рдкреГрд╖реНрда рдЙрдкрдпреЛрдЧреА рдерд╛?
0 / 5 - 0 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

caioremedio picture caioremedio  ┬╖  6рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

osterlind picture osterlind  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

Robuske picture Robuske  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

DamascenoRafael picture DamascenoRafael  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

TikhonovAlexander picture TikhonovAlexander  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ