web-dev-qa-db-fra.com

Créez une copie d'une UIView dans Swift

Étant donné que les objets sont des types de référence et non des types de valeur, si vous définissez un UIView égal à un autre UIView, les vues sont le même objet. Si vous modifiez l'un, vous modifierez également l'autre.

J'ai une situation intéressante où je voudrais ajouter un UIView comme sous-vue dans une autre vue, puis j'apporte quelques modifications, et ces modifications ne devraient pas affecter le UIView d'origine. Comment puis-je faire une copie du UIView pour pouvoir m'assurer que j'ajoute cette copie en tant que sous-vue au lieu d'une référence au UIView d'origine?

Notez que je ne peux pas recréer la vue de la même manière que l'original a été créé, j'ai besoin d'un moyen de créer une copie avec n'importe quel objet UIView.

42
Jordan H

Vous ne pouvez pas copier arbitrairement un objet. Seuls les objets qui implémentent le protocole NSCopying peuvent être copiés.

Cependant, il existe une solution de contournement: comme UIViews peut être sérialisé sur disque (par exemple pour charger à partir d'un XIB), vous pouvez utiliser NSKeyedArchiver et NSKeyedUnarchiver pour créer un sérialisé NSData décrivant votre vue, puis désérialisez-la à nouveau pour obtenir un objet indépendant mais identique.

19
uliwitness

Vous pouvez créer une extension UIView. Dans l'exemple d'extrait ci-dessous, la fonction copyView renvoie un AnyObject afin que vous puissiez copier n'importe quelle sous-classe d'un UIView, ie UIImageView. Si vous souhaitez copier niquement UIView, vous pouvez changer le type de retour en UIView.

//MARK: - UIView Extensions

extension UIView
{
    func copyView<T: UIView>() -> T {
        return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
    }
}

Exemple d'utilisation:

let sourceView = UIView()
let copiedView: UIView = sourceView.copyView()
111
Ivan Porkolab

Mise à jour pour iOS 12.

Les méthodes archivedData(withRootObject:) et unarchivedObject(with:) sont obsolètes à partir d'iOS 12.0.

Voici une mise à jour de la réponse de @Ivan Porcolab en utilisant la nouvelle API (depuis 11.0), également rendue plus générale pour prendre en charge d'autres types.

extension NSObject {
    func copyObject<T:NSObject>() throws -> T? {
        let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
        return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
    }
}
6
David James

Cette réponse montre comment faire ce que @uliwitness a suggéré. Autrement dit, obtenez un objet identique en l'archivant puis en le désarchivant. (C'est aussi essentiellement ce qu'a fait Ivan Porkolab dans sa réponse, mais dans un format plus lisible, je pense.)

let myView = UIView()

// create an NSData object from myView
let archive = NSKeyedArchiver.archivedData(withRootObject: myView)

// create a clone by unarchiving the NSData
let myViewCopy = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIView

Remarques

  • Le désarchivage des données crée un objet de type AnyObject. Nous avons utilisé as! UIView pour taper le convertir en UIView car nous savons que c'est ce que c'est. Si notre vue était un UITextView, nous pourrions taper cast it as! UITextView.
  • myViewCopy n'a plus de vue parent.
  • Certaines personnes mentionnent certains problèmes lors de l'utilisation de UIImage. Cependant, voir this et this answer.

Mise à jour vers Swift 3.0

4
Suragch

Je pense que vous devriez lier votre UIView avec un .nib et juste en créer un nouveau.

La propriété ne sera pas la même, mais vous conservez l'apparence et les méthodes.

4
Florian

Vous devez utiliser le modèle Prototype

Exemple du prototype:

class ThieveryCorporationPersonDisplay {
    var name: String?
    let font: String

    init(font: String) {
        self.font = font
    }

    func clone() -> ThieveryCorporationPersonDisplay {
        return ThieveryCorporationPersonDisplay(font:self.font)
    }
}

Un exemple d'utilisation de prototype:

let Prototype = ThieveryCorporationPersonDisplay(font:"Ubuntu")

let Philippe = Prototype.clone()
Philippe.name = "Philippe"

let Christoph = Prototype.clone()
Christoph.name = "Christoph"

let Eduardo = Prototype.clone()
Eduardo.name = "Eduardo"
1
Beslan Tularov

Une solution supplémentaire pourrait être de simplement créer un nouveau UIView, puis de copier toutes les propriétés critiques. Cela peut ne pas fonctionner dans le cas du PO, mais cela pourrait très bien fonctionner pour d'autres cas.

Par exemple, avec un UITextView, vous n'aurez probablement besoin que du cadre et du texte attribué:

let textViewCopy = UITextView(frame: textView.frame)
textViewCopy.attributedText = textView.attributedText 
0
Suragch