如何在同一播放器上编辑和更新号码?

发布于 2025-02-09 21:08:08 字数 10413 浏览 2 评论 0原文

我的应用程序部分基于画布。

#我想做什么?

我需要字典来跟踪和保存特定播放器的键和值。由于我想在键“ 1”上存储player1,player2在键“ 2”上,假设用户更改播放器2的数字,然后玩家2的数字而不是键“ 2”。

我可以放置多个玩家(CashApelayers-带有文字), 在ImageView上。并可以在同一玩家的双击上编辑文本标签。

我的问题是,每当我在同一播放器上单击第二次时,它也会显示错误的数字,当单击“好”会创建新播放器时。 我想要的是编辑带有不同数字的播放器标签(编号), 并且不要创建一个新的,直到我单击ImageView。

我已经尝试在触摸已达到的情况下创建Playerview,但我失败了,也尝试在其他资源上搜索相同的问题。

我添加了一些图像供参考。 ”添加不同的玩家编号”

class AddPlayerStruct {
    
    var addPlayerViewStruct : AddPlayerView?
    var addPlayerViewsArrStruct : [AddPlayerView] = []
    var Label = UILabel()
    
}
import UIKit
class ViewController: UIViewController {
    
    //MARK: AddPlayerView Variables
    
    var addPlayerView : AddPlayerView?
    var addPlayerViews: [AddPlayerView] = []
    var draggedAddPlayer: AddPlayerView?
    let addPlayerWidth : CGFloat = 40
    
    var addPlayerDict : [String : AddPlayerStruct] = [:]
    var playerCount : Int = 1
    
    var label = UILabel()
    var isDobuleClick : Bool = false
    
    
    @IBOutlet weak var images: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        guard let draggedAddPlayer = draggedAddPlayer, let point = touches.first?.location(in: images) else {
            return
        }
        draggedAddPlayer.center = point
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        // Do nothing if a circle is being dragged
        // or if we do not have a coordinate
        guard draggedAddPlayer == nil, let point = touches.first?.location(in: images) else {
            return
        }
        
        // Do not create new circle if touch is in an existing circle
        // Keep the reference of the (potentially) dragged circle
        if let draggedAddPlayer = addPlayerViews.filter({ UIBezierPath(ovalIn: $0.frame).contains(point) }).first {
            
            self.draggedAddPlayer = draggedAddPlayer
            return
        }
        
        
        // Create new circle and store in dict
        let rect = CGRect(x: point.x - 20, y: point.y - 20, width: addPlayerWidth, height: addPlayerWidth)
        addPlayerView = AddPlayerView(frame: rect)
        addPlayerView?.backgroundColor = .white
        addPlayerView?.isUserInteractionEnabled = true
        addPlayerView?.image = UIImage(named: "player")
        addPlayerView?.tintColor = .systemBlue
        
        
        addPlayerViews.append(addPlayerView!)
        images.addSubview(addPlayerView!)
        
        // The newly created view can be immediately dragged
        draggedAddPlayer = addPlayerView
        
        //Add Player label as Number
        //        playerCount = addPlayerDict.count + 1
        
        var addPlayerStruct = AddPlayerStruct()
        
        label = UILabel(frame: CGRect(x: rect.width / 2 - 8, y: rect.height / 2 + 5, width: 16, height: 10))
        
        if addPlayerStruct.Label.text == nil{
            label.text = String(addPlayerDict.count + 1)
        }
        label.font = UIFont(name: "Helvetica" , size: 10)
        label.textColor = UIColor.white
        label.textAlignment = NSTextAlignment.center
        label.isUserInteractionEnabled = true
        addPlayerView!.addSubview(label)
        
        
        debugPrint(addPlayerDict)
        
        
        addPlayerStruct.addPlayerViewStruct = addPlayerView
        addPlayerStruct.addPlayerViewsArrStruct.append(contentsOf: addPlayerViews)
        
        addPlayerDict.updateValue(addPlayerStruct, forKey: String(addPlayerDict.count + 1))
        debugPrint(addPlayerDict)
        
        
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        
        draggedAddPlayer = nil
        
        var selectedPlayerKey = String()
        var addPlayerStruct = AddPlayerStruct()
        var selectedPoint = CGPoint()
        
        if isDobuleClick == true {
            
            guard let point = touches.first?.location(in: images) else {
                return
            }
            
            
            //TODO: check which dict key's playerview has same point
            addPlayerDict.forEach { (key,value) in
                debugPrint(key)
                debugPrint(value)
                
                // debugPrint("before \(addPlayerDict.count)")
                //TODO: get selected dict key and get all data of it
                
                guard let points = value.addPlayerViewStruct?.frame.contains(point) else { return }
                if points{
                    selectedPlayerKey = key
                    
                    selectedPoint = point
                    addPlayerViews.append(contentsOf: value.addPlayerViewsArrStruct)
                    debugPrint(addPlayerViews.last)
                    addPlayerView = value.addPlayerViewStruct           //selected playerview
                    debugPrint(selectedPlayerKey)
                    
                    
                    // show data on alertview textfield
                    let alert = UIAlertController(title: "Player Number", message: "Enter new player Number", preferredStyle: .alert)
                    alert.addTextField { textData in
                        if addPlayerStruct.Label.text == nil{
                            textData.text = selectedPlayerKey
                            
                        }else{
                            textData.text = addPlayerStruct.Label.text
                        }
                    }
                    
                    // get changed data from textfield and save back to same dict key's playerview - Don't change key.
                    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                        let textfield = alert.textFields?[0]
                        addPlayerStruct.Label.text = textfield?.text!
                        
                        //   let index = self.addPlayerDict.index(forKey: selectedPlayerKey)
                        addPlayerStruct.addPlayerViewStruct = self.addPlayerView
                        self.draggedAddPlayer = self.addPlayerView
                        
                        self.addPlayerViews.removeLast()
                        self.addPlayerView?.removeFromSuperview()           //selected playerview removed from iamgeview
                        //
                        //                        //               Show on playerview
                        //
                        let rect = CGRect(x: selectedPoint.x - 20, y: selectedPoint.y - 20, width: self.addPlayerWidth, height: self.addPlayerWidth)
                        self.addPlayerView = AddPlayerView(frame: rect)
                        self.addPlayerView?.backgroundColor = .white
                        self.addPlayerView?.isUserInteractionEnabled = true
                        self.addPlayerView?.image = UIImage(named: "player")
                        self.addPlayerView?.tintColor = .systemBlue
                        
                        self.addPlayerViews.append(self.addPlayerView!)
                        self.images.addSubview(self.addPlayerView!)
                        
                        
                        
                        self.label = UILabel(frame: CGRect(x: rect.width / 2 - 8, y: rect.height / 2 + 5, width: 16, height: 10))
                        self.label.text = textfield?.text!
                        self.label.font = UIFont(name: "Helvetica" , size: 10)
                        self.label.textColor = UIColor.white
                        self.label.textAlignment = NSTextAlignment.center
                        self.label.isUserInteractionEnabled = true
                        self.addPlayerView!.addSubview(self.label)
                        
                        
                        
                        debugPrint(self.addPlayerDict.count)
                        self.addPlayerDict.updateValue(addPlayerStruct, forKey: selectedPlayerKey)
                        debugPrint(addPlayerStruct.addPlayerViewStruct?.frame)
                        debugPrint(self.addPlayerDict.count)
                    }))
                    self.present(alert, animated: false)
                }
                
            }
        }
        isDobuleClick = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            self.isDobuleClick = false
        }
        
    }
    
    
}

#addplayerview

class AddPlayerView : UIImageView{
    
    var shapeLayer = CAShapeLayer()
    var addPlayerPath = UIBezierPath()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup(){
        
        self.backgroundColor = .clear
        
        
        //        let shapeLayer = CAShapeLayer()
        addPlayerPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height / 2), radius: 20, startAngle: 0, endAngle: 360, clockwise: true)
        
        shapeLayer.path = addPlayerPath.cgPath
        shapeLayer.fillColor = UIColor.clear.cgColor
        self.layer.addSublayer(shapeLayer)
        
    }
}


my app part is based on canvas.

#What I want to do?

I need dictionary to track and save particular player's key and value.because, I want to store player1 on key "1" and player2 on key "2", suppose user change player 2's number, then player 2's number change not key "2".

I can place multiple players (cashapelayers - with text),
on imageview. and can edit text label on double click for same player.

my issue is, whenever i click second time on same player, it shows wrong number, as well when click okay it creates new player.
what i want is to edit a player's label(number) with different number,
and don't create a new one till I click on imageview.

I have try to create playerview in touchedEnded but I'm fail, also try to search for the same issue on other resources.

I have added some images for reference.
players on imageviewadd different player number
player number edit success
when click player 22 it shows as player 2
when click ok on alertview it creates new player

class AddPlayerStruct {
    
    var addPlayerViewStruct : AddPlayerView?
    var addPlayerViewsArrStruct : [AddPlayerView] = []
    var Label = UILabel()
    
}
import UIKit
class ViewController: UIViewController {
    
    //MARK: AddPlayerView Variables
    
    var addPlayerView : AddPlayerView?
    var addPlayerViews: [AddPlayerView] = []
    var draggedAddPlayer: AddPlayerView?
    let addPlayerWidth : CGFloat = 40
    
    var addPlayerDict : [String : AddPlayerStruct] = [:]
    var playerCount : Int = 1
    
    var label = UILabel()
    var isDobuleClick : Bool = false
    
    
    @IBOutlet weak var images: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        guard let draggedAddPlayer = draggedAddPlayer, let point = touches.first?.location(in: images) else {
            return
        }
        draggedAddPlayer.center = point
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        // Do nothing if a circle is being dragged
        // or if we do not have a coordinate
        guard draggedAddPlayer == nil, let point = touches.first?.location(in: images) else {
            return
        }
        
        // Do not create new circle if touch is in an existing circle
        // Keep the reference of the (potentially) dragged circle
        if let draggedAddPlayer = addPlayerViews.filter({ UIBezierPath(ovalIn: $0.frame).contains(point) }).first {
            
            self.draggedAddPlayer = draggedAddPlayer
            return
        }
        
        
        // Create new circle and store in dict
        let rect = CGRect(x: point.x - 20, y: point.y - 20, width: addPlayerWidth, height: addPlayerWidth)
        addPlayerView = AddPlayerView(frame: rect)
        addPlayerView?.backgroundColor = .white
        addPlayerView?.isUserInteractionEnabled = true
        addPlayerView?.image = UIImage(named: "player")
        addPlayerView?.tintColor = .systemBlue
        
        
        addPlayerViews.append(addPlayerView!)
        images.addSubview(addPlayerView!)
        
        // The newly created view can be immediately dragged
        draggedAddPlayer = addPlayerView
        
        //Add Player label as Number
        //        playerCount = addPlayerDict.count + 1
        
        var addPlayerStruct = AddPlayerStruct()
        
        label = UILabel(frame: CGRect(x: rect.width / 2 - 8, y: rect.height / 2 + 5, width: 16, height: 10))
        
        if addPlayerStruct.Label.text == nil{
            label.text = String(addPlayerDict.count + 1)
        }
        label.font = UIFont(name: "Helvetica" , size: 10)
        label.textColor = UIColor.white
        label.textAlignment = NSTextAlignment.center
        label.isUserInteractionEnabled = true
        addPlayerView!.addSubview(label)
        
        
        debugPrint(addPlayerDict)
        
        
        addPlayerStruct.addPlayerViewStruct = addPlayerView
        addPlayerStruct.addPlayerViewsArrStruct.append(contentsOf: addPlayerViews)
        
        addPlayerDict.updateValue(addPlayerStruct, forKey: String(addPlayerDict.count + 1))
        debugPrint(addPlayerDict)
        
        
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        
        draggedAddPlayer = nil
        
        var selectedPlayerKey = String()
        var addPlayerStruct = AddPlayerStruct()
        var selectedPoint = CGPoint()
        
        if isDobuleClick == true {
            
            guard let point = touches.first?.location(in: images) else {
                return
            }
            
            
            //TODO: check which dict key's playerview has same point
            addPlayerDict.forEach { (key,value) in
                debugPrint(key)
                debugPrint(value)
                
                // debugPrint("before \(addPlayerDict.count)")
                //TODO: get selected dict key and get all data of it
                
                guard let points = value.addPlayerViewStruct?.frame.contains(point) else { return }
                if points{
                    selectedPlayerKey = key
                    
                    selectedPoint = point
                    addPlayerViews.append(contentsOf: value.addPlayerViewsArrStruct)
                    debugPrint(addPlayerViews.last)
                    addPlayerView = value.addPlayerViewStruct           //selected playerview
                    debugPrint(selectedPlayerKey)
                    
                    
                    // show data on alertview textfield
                    let alert = UIAlertController(title: "Player Number", message: "Enter new player Number", preferredStyle: .alert)
                    alert.addTextField { textData in
                        if addPlayerStruct.Label.text == nil{
                            textData.text = selectedPlayerKey
                            
                        }else{
                            textData.text = addPlayerStruct.Label.text
                        }
                    }
                    
                    // get changed data from textfield and save back to same dict key's playerview - Don't change key.
                    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                        let textfield = alert.textFields?[0]
                        addPlayerStruct.Label.text = textfield?.text!
                        
                        //   let index = self.addPlayerDict.index(forKey: selectedPlayerKey)
                        addPlayerStruct.addPlayerViewStruct = self.addPlayerView
                        self.draggedAddPlayer = self.addPlayerView
                        
                        self.addPlayerViews.removeLast()
                        self.addPlayerView?.removeFromSuperview()           //selected playerview removed from iamgeview
                        //
                        //                        //               Show on playerview
                        //
                        let rect = CGRect(x: selectedPoint.x - 20, y: selectedPoint.y - 20, width: self.addPlayerWidth, height: self.addPlayerWidth)
                        self.addPlayerView = AddPlayerView(frame: rect)
                        self.addPlayerView?.backgroundColor = .white
                        self.addPlayerView?.isUserInteractionEnabled = true
                        self.addPlayerView?.image = UIImage(named: "player")
                        self.addPlayerView?.tintColor = .systemBlue
                        
                        self.addPlayerViews.append(self.addPlayerView!)
                        self.images.addSubview(self.addPlayerView!)
                        
                        
                        
                        self.label = UILabel(frame: CGRect(x: rect.width / 2 - 8, y: rect.height / 2 + 5, width: 16, height: 10))
                        self.label.text = textfield?.text!
                        self.label.font = UIFont(name: "Helvetica" , size: 10)
                        self.label.textColor = UIColor.white
                        self.label.textAlignment = NSTextAlignment.center
                        self.label.isUserInteractionEnabled = true
                        self.addPlayerView!.addSubview(self.label)
                        
                        
                        
                        debugPrint(self.addPlayerDict.count)
                        self.addPlayerDict.updateValue(addPlayerStruct, forKey: selectedPlayerKey)
                        debugPrint(addPlayerStruct.addPlayerViewStruct?.frame)
                        debugPrint(self.addPlayerDict.count)
                    }))
                    self.present(alert, animated: false)
                }
                
            }
        }
        isDobuleClick = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            self.isDobuleClick = false
        }
        
    }
    
    
}

#AddPlayerView

class AddPlayerView : UIImageView{
    
    var shapeLayer = CAShapeLayer()
    var addPlayerPath = UIBezierPath()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup(){
        
        self.backgroundColor = .clear
        
        
        //        let shapeLayer = CAShapeLayer()
        addPlayerPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height / 2), radius: 20, startAngle: 0, endAngle: 360, clockwise: true)
        
        shapeLayer.path = addPlayerPath.cgPath
        shapeLayer.fillColor = UIColor.clear.cgColor
        self.layer.addSublayer(shapeLayer)
        
    }
}


如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

安穩 2025-02-16 21:08:08

您有一个不错的方法,但是有几个提示...

您的班级和VAR命名相当令人困惑。例如,而不是命名图像视图图像命名它更有意义,例如PlayfieldImageView。并且,而不是addplayerview(听起来像是一个动作),只需命名PlayerView

您无需保留单独的播放器视图或播放器构造字典...当您添加PlayerView作为PlayfieldimageView的子视图时,您可以跟踪它带有.subviews PlayfieldImageView的属性。

您可以将您使用的大量逻辑移动到播放器视图 沿着这些行:

class PlayerView : UIImageView {
    
    public var playerNumber: Int = 0 {
        didSet {
            playerLabel.text = "\(playerNumber)"
        }
    }
    
    private let shapeLayer = CAShapeLayer()
    
    private let playerLabel: UILabel = {
        let v = UILabel()
        v.font = UIFont(name: "Helvetica" , size: 10)
        v.textColor = .white
        v.textAlignment = NSTextAlignment.center
        v.text = "0"
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    private func setup() {
        self.isUserInteractionEnabled = true
        self.backgroundColor = .white
        self.tintColor = .systemBlue
        
        if let img = UIImage(named: "player") {
            self.image = img
        } else {
            if let img = UIImage(systemName: "person.fill") {
                self.image = img
            } else {
                self.backgroundColor = .green
            }
        }
        
        // if using as a mask, can be any opaque color
        shapeLayer.fillColor = UIColor.black.cgColor
        
        // assuming you want a "round" view
        //self.layer.addSublayer(shapeLayer)
        self.layer.mask = shapeLayer
        
        // add and constrain the label as a subview
        addSubview(playerLabel)
        NSLayoutConstraint.activate([
            playerLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            playerLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
            playerLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5.0),
        ])
        
    }
    override func layoutSubviews() {
        // update the mask path here (we have accurate bounds)
        shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
    }
}

现在您可以添加一个新的PlayerView并分配其.playerNumber ...而不是所有设置的图像,添加标签等。 < / code>属性,而不是删除 /重新创建视图。

当尝试触摸触摸 taps(尤其是双敲击)时,在子视图本身中添加双重击中手势识别器要容易得多。

这是相同的playerview类,但是有一个手势识别器 - 并封闭以告诉控制器它是双敲打的:

class PlayerView : UIImageView {

    // closure to tell the controller this view was double-tapped
    public var gotDoubleTap: ((PlayerView) -> ())?
    
    public var playerNumber: Int = 0 {
        didSet {
            playerLabel.text = "\(playerNumber)"
        }
    }
    
    private let shapeLayer = CAShapeLayer()
    
    private let playerLabel: UILabel = {
        let v = UILabel()
        v.font = UIFont(name: "Helvetica" , size: 10)
        v.textColor = .white
        v.textAlignment = NSTextAlignment.center
        v.text = "0"
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    private func setup() {
        self.isUserInteractionEnabled = true
        self.backgroundColor = .white
        self.tintColor = .systemBlue
        
        if let img = UIImage(named: "player") {
            self.image = img
        } else {
            if let img = UIImage(systemName: "person.fill") {
                self.image = img
            } else {
                self.backgroundColor = .green
            }
        }
        
        // if using as a mask, can be any opaque color
        shapeLayer.fillColor = UIColor.black.cgColor
        
        // assuming you want a "round" view
        //self.layer.addSublayer(shapeLayer)
        self.layer.mask = shapeLayer
        
        // add and constrain the label as a subview
        addSubview(playerLabel)
        NSLayoutConstraint.activate([
            playerLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            playerLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
            playerLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5.0),
        ])
        
        // add a double-tap gesture recognizer
        let g = UITapGestureRecognizer(target: self, action: #selector(doubleTapHandler(_:)))
        g.numberOfTapsRequired = 2
        addGestureRecognizer(g)
    }
    override func layoutSubviews() {
        // update the mask path here (we have accurate bounds)
        shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
    }
    @objc func doubleTapHandler(_ g: UITapGestureRecognizer) {
        // tell the controller we were double-tapped
        gotDoubleTap?(self)
    }
}

您现在可以简化所有触摸代码,以便只添加新的或移动新的触摸代码播放器视图。

这是一个示例控制器类,使用上述PlayerView类:

class AddPlayerViewController: UIViewController {
    
    //MARK: AddPlayerView Variables
    
    // we can declare this as a standard UIView
    var draggedPlayerView: UIView?
    
    let playerViewWidth : CGFloat = 40
    
    @IBOutlet var playingFieldImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        playingFieldImageView.isUserInteractionEnabled = true
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let draggedAddPlayer = draggedPlayerView, let point = touches.first?.location(in: playingFieldImageView) else {
            return
        }
        draggedAddPlayer.center = point
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        // Do nothing if a PlayerView is being dragged
        // or if we do not have a coordinate
        guard draggedPlayerView == nil, let point = touches.first?.location(in: playingFieldImageView) else {
            return
        }
        
        // Do not create new PlayerView if touch is in an existing circle
        // Keep the reference of the (potentially) dragged circle
        //  filter only subviews which are PlayerView class (in case we've added another subview type)
        if let draggedAddPlayer = playingFieldImageView.subviews.filter({ $0 is PlayerView }).filter({ UIBezierPath(ovalIn: $0.frame).contains(point) }).first {
            self.draggedPlayerView = draggedAddPlayer
            return
        }
        
        // Create new PlayerView
        let rect = CGRect(x: point.x - 20, y: point.y - 20, width: playerViewWidth, height: playerViewWidth)
        let newPlayerView = PlayerView(frame: rect)
        
        // give the new player the lowest available number
        //  for example, if we've created numbers:
        //      "1" "2" "3" "4" "5"
        //  we want to assign "6" to the new guy
        //  but... if the user has changed the 3rd player to "12"
        //  we'll have players:
        //      "1" "2" "12" "4" "5"
        //  so we want to assign "3" to the new guy
        let nums = playingFieldImageView.subviews.compactMap{$0 as? PlayerView}.compactMap { $0.playerNumber }
        var newNum: Int = 1
        for i in 1..<nums.count + 2 {
            if !nums.contains(i) {
                newNum = i
                break
            }
        }

        newPlayerView.playerNumber = newNum
        
        // set the double-tap closure
        newPlayerView.gotDoubleTap  = { [weak self] pv in
            guard let self = self else { return }
            self.changePlayerNumber(pv)
        }
        
        playingFieldImageView.addSubview(newPlayerView)
        
        // The newly created view can be immediately dragged
        draggedPlayerView = newPlayerView
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        draggedPlayerView = nil
    }

    // called when a PlayerView is double-tapped
    func changePlayerNumber(_ playerView: PlayerView) {

        // show data on alertview textfield
        let alert = UIAlertController(title: "Player Number", message: "Enter new player Number", preferredStyle: .alert)

        alert.addTextField { textData in
            textData.text = "\(playerView.playerNumber)"
        }
        
        // get changed data from textfield and update the playerNumber for the PlayerView
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
            guard let tf = alert.textFields?.first,
               let newNumString = tf.text,
               let newNumInt = Int(newNumString),
                  newNumInt != playerView.playerNumber
            else {
                // user entered something that was not a number, or
                //  tapped OK without changing the number
                return
            }
            // don't allow a duplicate player number
            let nums = self.playingFieldImageView.subviews.compactMap{$0 as? PlayerView}.compactMap { $0.playerNumber }
            if nums.contains(newNumInt) {
                let dupAlert = UIAlertController(title: "Duplicate PLayer Number", message: "Somebody else already has number: \(newNumInt)", preferredStyle: .alert)
                dupAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                    self.changePlayerNumber(playerView)
                }))
                self.present(dupAlert, animated: true)
            } else {
                playerView.playerNumber = newNumInt
            }
        }))
        
        self.present(alert, animated: false)

    }

}

You have a decent approach, but a couple tips...

Your class and var naming is rather confusing. For example, instead of naming your image view images it makes much more sense to name it something like playingFieldImageView. And, instead of AddPlayerView (which sounds like an action), just name it PlayerView.

You don't need to keep a separate array of player views or a dictionary of player structs... When you add a PlayerView as a subview of playingFieldImageView, you can track it with the .subviews property of playingFieldImageView.

You can move a lot of the logic you're using to manage the player view into the player view class... along these lines:

class PlayerView : UIImageView {
    
    public var playerNumber: Int = 0 {
        didSet {
            playerLabel.text = "\(playerNumber)"
        }
    }
    
    private let shapeLayer = CAShapeLayer()
    
    private let playerLabel: UILabel = {
        let v = UILabel()
        v.font = UIFont(name: "Helvetica" , size: 10)
        v.textColor = .white
        v.textAlignment = NSTextAlignment.center
        v.text = "0"
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    private func setup() {
        self.isUserInteractionEnabled = true
        self.backgroundColor = .white
        self.tintColor = .systemBlue
        
        if let img = UIImage(named: "player") {
            self.image = img
        } else {
            if let img = UIImage(systemName: "person.fill") {
                self.image = img
            } else {
                self.backgroundColor = .green
            }
        }
        
        // if using as a mask, can be any opaque color
        shapeLayer.fillColor = UIColor.black.cgColor
        
        // assuming you want a "round" view
        //self.layer.addSublayer(shapeLayer)
        self.layer.mask = shapeLayer
        
        // add and constrain the label as a subview
        addSubview(playerLabel)
        NSLayoutConstraint.activate([
            playerLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            playerLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
            playerLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5.0),
        ])
        
    }
    override func layoutSubviews() {
        // update the mask path here (we have accurate bounds)
        shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
    }
}

Now you can add a new PlayerView and assign its .playerNumber ... instead of all of the set image, add label, etc. And, when the user wants to change the "player number" you can update the .playerNumber property instead of removing / re-creating views.

When trying to track touches and taps (particularly double-taps), it is much easier to add a double-tap gesture recognizer to the subview itself.

Here's that same PlayerView class, but with a gesture recognizer -- and a closure to tell the controller that it was double-tapped:

class PlayerView : UIImageView {

    // closure to tell the controller this view was double-tapped
    public var gotDoubleTap: ((PlayerView) -> ())?
    
    public var playerNumber: Int = 0 {
        didSet {
            playerLabel.text = "\(playerNumber)"
        }
    }
    
    private let shapeLayer = CAShapeLayer()
    
    private let playerLabel: UILabel = {
        let v = UILabel()
        v.font = UIFont(name: "Helvetica" , size: 10)
        v.textColor = .white
        v.textAlignment = NSTextAlignment.center
        v.text = "0"
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    private func setup() {
        self.isUserInteractionEnabled = true
        self.backgroundColor = .white
        self.tintColor = .systemBlue
        
        if let img = UIImage(named: "player") {
            self.image = img
        } else {
            if let img = UIImage(systemName: "person.fill") {
                self.image = img
            } else {
                self.backgroundColor = .green
            }
        }
        
        // if using as a mask, can be any opaque color
        shapeLayer.fillColor = UIColor.black.cgColor
        
        // assuming you want a "round" view
        //self.layer.addSublayer(shapeLayer)
        self.layer.mask = shapeLayer
        
        // add and constrain the label as a subview
        addSubview(playerLabel)
        NSLayoutConstraint.activate([
            playerLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            playerLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
            playerLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5.0),
        ])
        
        // add a double-tap gesture recognizer
        let g = UITapGestureRecognizer(target: self, action: #selector(doubleTapHandler(_:)))
        g.numberOfTapsRequired = 2
        addGestureRecognizer(g)
    }
    override func layoutSubviews() {
        // update the mask path here (we have accurate bounds)
        shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
    }
    @objc func doubleTapHandler(_ g: UITapGestureRecognizer) {
        // tell the controller we were double-tapped
        gotDoubleTap?(self)
    }
}

You can now simplify all of your touch code to handle only adding new or moving the player views.

Here's an example controller class, using the above PlayerView class:

class AddPlayerViewController: UIViewController {
    
    //MARK: AddPlayerView Variables
    
    // we can declare this as a standard UIView
    var draggedPlayerView: UIView?
    
    let playerViewWidth : CGFloat = 40
    
    @IBOutlet var playingFieldImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        playingFieldImageView.isUserInteractionEnabled = true
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let draggedAddPlayer = draggedPlayerView, let point = touches.first?.location(in: playingFieldImageView) else {
            return
        }
        draggedAddPlayer.center = point
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        // Do nothing if a PlayerView is being dragged
        // or if we do not have a coordinate
        guard draggedPlayerView == nil, let point = touches.first?.location(in: playingFieldImageView) else {
            return
        }
        
        // Do not create new PlayerView if touch is in an existing circle
        // Keep the reference of the (potentially) dragged circle
        //  filter only subviews which are PlayerView class (in case we've added another subview type)
        if let draggedAddPlayer = playingFieldImageView.subviews.filter({ $0 is PlayerView }).filter({ UIBezierPath(ovalIn: $0.frame).contains(point) }).first {
            self.draggedPlayerView = draggedAddPlayer
            return
        }
        
        // Create new PlayerView
        let rect = CGRect(x: point.x - 20, y: point.y - 20, width: playerViewWidth, height: playerViewWidth)
        let newPlayerView = PlayerView(frame: rect)
        
        // give the new player the lowest available number
        //  for example, if we've created numbers:
        //      "1" "2" "3" "4" "5"
        //  we want to assign "6" to the new guy
        //  but... if the user has changed the 3rd player to "12"
        //  we'll have players:
        //      "1" "2" "12" "4" "5"
        //  so we want to assign "3" to the new guy
        let nums = playingFieldImageView.subviews.compactMap{$0 as? PlayerView}.compactMap { $0.playerNumber }
        var newNum: Int = 1
        for i in 1..<nums.count + 2 {
            if !nums.contains(i) {
                newNum = i
                break
            }
        }

        newPlayerView.playerNumber = newNum
        
        // set the double-tap closure
        newPlayerView.gotDoubleTap  = { [weak self] pv in
            guard let self = self else { return }
            self.changePlayerNumber(pv)
        }
        
        playingFieldImageView.addSubview(newPlayerView)
        
        // The newly created view can be immediately dragged
        draggedPlayerView = newPlayerView
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        draggedPlayerView = nil
    }

    // called when a PlayerView is double-tapped
    func changePlayerNumber(_ playerView: PlayerView) {

        // show data on alertview textfield
        let alert = UIAlertController(title: "Player Number", message: "Enter new player Number", preferredStyle: .alert)

        alert.addTextField { textData in
            textData.text = "\(playerView.playerNumber)"
        }
        
        // get changed data from textfield and update the playerNumber for the PlayerView
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
            guard let tf = alert.textFields?.first,
               let newNumString = tf.text,
               let newNumInt = Int(newNumString),
                  newNumInt != playerView.playerNumber
            else {
                // user entered something that was not a number, or
                //  tapped OK without changing the number
                return
            }
            // don't allow a duplicate player number
            let nums = self.playingFieldImageView.subviews.compactMap{$0 as? PlayerView}.compactMap { $0.playerNumber }
            if nums.contains(newNumInt) {
                let dupAlert = UIAlertController(title: "Duplicate PLayer Number", message: "Somebody else already has number: \(newNumInt)", preferredStyle: .alert)
                dupAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                    self.changePlayerNumber(playerView)
                }))
                self.present(dupAlert, animated: true)
            } else {
                playerView.playerNumber = newNumInt
            }
        }))
        
        self.present(alert, animated: false)

    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文