ios – Particular Sparring Mode Causes Incorrect Node Redirection

0
17
ios – Particular Sparring Mode Causes Incorrect Node Redirection


I’m engaged on an iOS app utilizing UIKit and Core Information that entails graph-based navigation the place nodes characterize completely different strikes in a martial arts system. Customers can navigate by way of these nodes in numerous modes, together with Particular Sparring Mode, which introduces randomness in node choice.

The whole lot works positive till a Fail Node seems. At that time:

A standard node begins incorrectly redirecting to a Fail Node’s Major as an alternative of its supposed connections.
This situation persists till the person refreshes the web page.
Fail Nodes themselves by no means redirect incorrectly, which means solely regular nodes get corrupted.

Anticipated Conduct –

Solely Secondary Nodes ought to redirect to their corresponding Major Nodes.
Fail Nodes mustn’t intervene with the navigation logic of Regular Nodes.

Debugging navigateToPrimaryNode(for:)
I added print() statements to trace which nodes had been being redirected.
Discovered that after encountering a Fail Node, a standard node unexpectedly began redirecting to a Fail Node’s Major.
Checking startSpecificSparring(for:)
I logged the nodes that had been being hidden or proven.
Verified that Fail Nodes had been being displayed appropriately, however some regular nodes had been disappearing or redirecting incorrectly.
Investigating updateLinePosition(for:) and redrawLines()
I checked whether or not incorrect traces had been being drawn between nodes after encountering a Fail Node.
Discovered that the visible illustration of the nodes appeared positive, which means the difficulty wasn’t purely a UI bug.
Resetting & Retrying Particular Sparring Mode
I used retrySpecificSparring() to see if the difficulty resolved itself on a retry.
The problem persevered even after retrying, which means one thing was being modified persistently.
Verifying restoreTextFields()
Restarted the app to see if Core Information was saving incorrect relationships.
Confirmed that the difficulty disappears after refreshing, which means it’s probably not a Core Information storage situation, however one thing taking place dynamically throughout navigation.
What I Anticipated

Secondary nodes ought to solely redirect to their respective Major nodes.
Fail Nodes mustn’t intervene with regular nodes.
After a failure, regular nodes ought to proceed functioning as anticipated.
Retrying Particular Sparring ought to reset node conduct correctly.

Findings –
Regular nodes typically get “caught” redirecting to a Fail Node’s Major.
The problem solely occurs after encountering a Fail Node.
Refreshing the app fixes the difficulty, which means it isn’t being saved incorrectly in Core Information.
The issue persists throughout retries however disappears on full web page refresh.

That is all of my relative code items.

func navigateToPrimaryNode(for node: DragableNode) -> DragableNode {
    // ✅ If node is already major, return it instantly
    if node.sort == .major {
        return node
    }

    // ✅ If node is secondary, discover its appropriate major node
    if node.sort == .secondary, let primaryNode = findPrimaryNode(for: node) {
        print("🔄 Navigating Secondary (node.textual content ?? "Unnamed") to Major (primaryNode.textual content ?? "Unnamed")")
        return primaryNode
    }

    // ✅ If it is a fail node, verify if it has a major fail node
    if node.entity.nodeType == .fail, let primaryFailNode = findPrimaryNode(for: node) {
        print("⚠️ Redirecting Fail Node (node.textual content ?? "Unnamed") to Major (primaryFailNode.textual content ?? "Unnamed")")
        return primaryFailNode
    }

    // 🔴 Particular Case: Stop fail nodes from overriding regular node redirects
    if node.entity.nodeType != .fail {
        if let cachedPrimary = findPrimaryNode(for: node) {
            print("✅ Making certain regular node (node.textual content ?? "Unnamed") stays mapped to its precise major (cachedPrimary.textual content ?? "Unnamed")")
            return cachedPrimary
        }
    }

    print("⚠️ No major discovered for (node.textual content ?? "Unnamed"), returning authentic node")
    return node
}

Piece 2

func findPrimaryNode(for node: DragableNode) -> DragableNode? {
    guard node.sort == .secondary else { return nil }

    let primaryCandidates = textFields.filter {
        $0.sort == .major && $0.textual content == node.textual content
    }

    if primaryCandidates.isEmpty {
        print("⚠️ No major node discovered for secondary node: (node.textual content ?? "Unnamed")")
        return nil
    }

    if primaryCandidates.rely == 1 {
        print("✅ Discovered single major node: (primaryCandidates.first!.textual content ?? "Unnamed")")
        return primaryCandidates.first
    }

    // 🔥 If a number of major nodes exist, use the entity relationship as a backup verify
    for major in primaryCandidates {
        if major.entity.kids?.accommodates(node.entity) == true {
            print("✅ Chosen appropriate major node: (major.textual content ?? "Unnamed") for secondary node: (node.textual content ?? "Unnamed")")
            return major
        }
    }

    print("⚠️ A number of matching major nodes discovered, defaulting to first: (primaryCandidates.first!.textual content ?? "Unnamed")")
    return primaryCandidates.first
}

piece 3

// Helper operate to search out the first textual content area with a selected identify
func findPrimaryTextField(named identify: String) -> DragableNode? {
    return textFields.first { $0.textual content == identify && $0.sort == .major }
}

piece 4

@objc func handleNodeTap(_ sender: UITapGestureRecognizer) {
    guard let tappedNode = sender.view as? DragableNode else { return }

    // ✅ If tapped node is a Secondary Node, redirect to the Major Node
        if tappedNode.sort == .secondary, let primaryNode = findPrimaryNode(for: tappedNode) {
            print("🔄 Redirecting from Secondary (tappedNode.textual content ?? "Unnamed") to Major (primaryNode.textual content ?? "Unnamed")")
            enterViewMode(for: primaryNode)
            return
        }
    
    
    
    if isInViewMode {
          enterViewMode(for: tappedNode)
          return
      }

    if isInSpecificSparring {
           print("🔥 Particular Sparring: Transferring to Subsequent Stage from (tappedNode.textual content ?? "Unnamed Node")")
           startSpecificSparring(for: tappedNode, problem: currentSparringDifficulty)

           // ✅ Recenter and zoom to suit new nodes after choosing one
           DispatchQueue.foremost.asyncAfter(deadline: .now() + 0.1) {
               let visibleNodes = self.contentView.subviews.compactMap { $0 as? DragableNode }.filter { !$0.isHidden }
               self.recenterAndZoomToFit(nodes: visibleNodes)
           }
           return
       }

    // Animate the present node scaling up
    UIView.animate(withDuration: 0.3, animations: {
        tappedNode.rework = CGAffineTransform(scaleX: 1.2, y: 1.2)
        tappedNode.alpha = 1.0
    }, completion: { _ in
        // Reset the scaling again to regular
        UIView.animate(withDuration: 0.2) {
            tappedNode.rework = CGAffineTransform.id
        }

        // 🔥 Transition to the brand new node
        self.enterViewMode(for: tappedNode)
    })
}

piece 5

@objc non-public func retrySpecificSparring() {
    guard let firstNode = firstViewModeNode else {
    print("⚠️ No firstViewModeNode discovered! Restarting from the final used node.")

        if let lastUsedNode = textFields.first(the place: { !$0.isHidden }) {
            firstViewModeNode = lastUsedNode
            startSpecificSparring(for: lastUsedNode, problem: currentSparringDifficulty)
        }
        return
    }

    print("🔄 Retrying Particular Sparring from (firstNode.textual content ?? "Unnamed")")

    // ✅ Resetting the View
    DispatchQueue.foremost.async {
        for subview in self.contentView.subviews {
            if let textNode = subview as? DragableNode {
                textNode.isHidden = true
                textNode.alpha = 0.0
            }
        }

        // ✅ Eradicating previous traces
        self.contentView.layer.sublayers?.removeAll(the place: { $0 is CAShapeLayer })

        // ✅ Restart sparring
        self.startSpecificSparring(for: firstNode, problem: self.currentSparringDifficulty)
    }
}

piece 6

func startSpecificSparring(for node: DragableNode, problem: Int) {
    guard let superview = self.contentView else { return }

    var targetNode: DragableNode = navigateToPrimaryNode(for: node)

    // Debugging
    print("🔥 Beginning Particular Sparring - Node: (node.textual content ?? "Unnamed") (Sort: (node.sort))")
    print("🔄 Redirecting to Major Node: (targetNode.textual content ?? "Unnamed") (Sort: (targetNode.sort))")

    if firstViewModeNode == nil {
        firstViewModeNode = targetNode
        print("✅ Setting firstViewModeNode to (targetNode.textual content ?? "Unnamed Node")")
    }

    // ✅ Guarantee the proper node is seen and up to date instantly
    DispatchQueue.foremost.async {
        targetNode.isHidden = false
        targetNode.alpha = 1.0
        self.contentView.bringSubviewToFront(targetNode)
    }

  

    // 🔥 Guarantee major nodes are delivered to the entrance to keep away from contact interference
       for subview in contentView.subviews {
           if let primaryNode = subview as? DragableNode, primaryNode.sort == .major {
               contentView.bringSubviewToFront(primaryNode)
           }
       }

 
    isInSpecificSparring = true
    currentSparringDifficulty = problem

    // ✅ Guarantee firstViewModeNode is ready when coming into sparring mode
        if firstViewModeNode == nil {
            firstViewModeNode = targetNode
        }
    
    // ✅ Roll a random quantity (1-10)
    let winChance = Int.random(in: 1...10)
    
    // ✅ Decide successful/shedding numbers primarily based on problem
        let (winningNumbers, losingNumbers) = getWinningLosingNumbers(for: problem)

        let didWin = winningNumbers.accommodates(winChance)

    print("🎲 Random Roll: (winChance) → (didWin ? "SUCCESS" : "FAIL")")

    // ✅ Cover all nodes initially
    for subview in superview.subviews {
        if let textNode = subview as? DragableNode {
            textNode.isHidden = true
        }
    }

    // ✅ At all times preserve the chosen node seen
    targetNode.isHidden = false
    targetNode.alpha = 1.0
    superview.bringSubviewToFront(targetNode)

    // ✅ Take away all current traces
    for sublayer in superview.layer.sublayers ?? [] {
        if let line = sublayer as? CAShapeLayer {
            line.removeFromSuperlayer()
        }
    }

    var nodesToShow: [DragableNode] = []

    // ✅ Decide which nodes ought to be seen
    for subview in superview.subviews {
        if let textNode = subview as? DragableNode, textNode !== targetNode {
            let isConnected = targetNode.entity.kids?.accommodates(the place: { ($0 as? TextFieldsEntity) == textNode.entity }) == true
            let isParent = textNode.entity.dad or mum == targetNode.entity
            let isFailNode = textNode.entity.nodeType == .fail
            let isAllowedSuccessType = textNode.entity.nodeType == .development ||
                                       textNode.entity.nodeType == .transition ||
                                       textNode.entity.nodeType == .submission


            print("🟢 Checking node: (textNode.textual content ?? "Unnamed") - Sort: (textNode.entity.nodeType)")

            if didWin {
                // ✅ Cover all fail nodes on success
                if isFailNode {
                    textNode.isHidden = true
                    print("❌ Hiding Fail Node: (textNode.textual content ?? "Unnamed") (Success Path)")
                }
                // ✅ Solely present success nodes
                else if isConnected && isAllowedSuccessType {
                    print("✅ Exhibiting: (textNode.textual content ?? "Unnamed") (Success Path)")
                    nodesToShow.append(textNode)
                }
            } else {
                // ✅ Solely present fail nodes
                if isConnected && isFailNode {
                    print("✅ Exhibiting Fail Node: (textNode.textual content ?? "Unnamed") (Fail Path)")
                    nodesToShow.append(textNode)
                }
            }
        }
    }

    // ✅ Guarantee solely the proper nodes are seen
    for node in nodesToShow {
        node.isHidden = false
        node.alpha = 1.0
    }

    // ✅ Fade in linked nodes easily
    fadeInConnectedNodes(nodesToShow)

    // ✅ Place nodes correctly
    positionNodesRelativeTo(targetNode, connectedNodes: nodesToShow)

    // ✅ Auto-recenter the view to suit all seen nodes
       recenterAndZoomToFit(nodes: [targetNode] + nodesToShow)
    
    DispatchQueue.foremost.asyncAfter(deadline: .now() + 0.1) {
        let visibleNodes = [targetNode] + nodesToShow
        self.recenterAndZoomToFit(nodes: visibleNodes)
    }

   
    // ✅ Add Retry & Exit buttons
    addRetryAndExitButtons(under: targetNode)
}

And right here is the node supervisor and dragable Node in case you want it

import Basis
import UIKit

class NodeManager {
    static let shared = NodeManager()

    var primaryNodes: [String: (node: DragableNode, viewController: UIViewController)] = [:]
    
    func clearPrimaryNodes() {
          print("🧹 Clearing cached major nodes")
          primaryNodes.removeAll()
      }
    
    func addPrimaryNode(_ node: DragableNode, viewController: UIViewController) {
        primaryNodes[node.text ?? ""] = (node, viewController)
    }
    
    func findPrimaryNode(named identify: String) -> (node: DragableNode, viewController: UIViewController)? {
        return primaryNodes[name]
    }

}

import UIKit

    class DragableNode: UIView, UITextFieldDelegate {
        
      
    
        var textField: UITextField
        var entity: TextFieldsEntity
        var traces: [CAShapeLayer] = []
        non-public var panGesture: UIPanGestureRecognizer!
        var isInEditMode = false
        var isEditable: Bool = false {
            didSet {
                textField.isUserInteractionEnabled = isEditable
            }
        }
        
        var identifier: UUID?
        var isPrimary: Bool = false
        var isSecondary: Bool = false
        
        var textual content: String? {
            get { return textField.textual content }
            set { textField.textual content = newValue}
        }
        
        var attributedPlaceholder: NSAttributedString? {
            get { return textField.attributedPlaceholder }
            set { textField.attributedPlaceholder = newValue }
        }
        
        var sort: TextFieldType = .none {
            didSet {
                typeChanged(sort: sort)
            }
        }
        // END OF STAGE
        
        init(dimension: CGSize, entity: TextFieldsEntity) {
            let body = CGRect(x: entity.xPosition, y: entity.yPosition, width: dimension.width, peak: dimension.peak)
            self.textField = UITextField(body: body)
            self.entity = entity
            tremendous.init(body: body)
            self.sort = entity.fieldType
            typeChanged(sort: sort)
            setupView()
            setupGestures()
            NotificationCenter.default.addObserver(self, selector: #selector(handleEditModeChanged(_:)), identify: .editModeChanged, object: nil)
        }
        
        init(body: CGRect, entity: TextFieldsEntity) {
            self.textField = UITextField(body: body)
            self.entity = entity
            tremendous.init(body: body)
            self.sort = entity.fieldType
            typeChanged(sort: sort)
            setupView()
            setupGestures()
            NotificationCenter.default.addObserver(self, selector: #selector(handleEditModeChanged(_:)), identify: .editModeChanged, object: nil)
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("Technique not accessible")
        }
        
        override func canPerformAction(_ motion: Selector, withSender sender: Any?) -> Bool {
            if motion == #selector(UIResponderStandardEditActions.selectAll(_:)) {
                return false
            }
            return tremendous.canPerformAction(motion, withSender: sender)
        }
        
        non-public func setupView() {
            textField.delegate = self
            addSubview(textField)
            textField.translatesAutoresizingMaskIntoConstraints = false
            
            // ✅ Apply default background coloration (strong coloration for textual content fields)
               backgroundColor = entity.nodeType.coloration.withAlphaComponent(0.3)
    
               // ✅ Apply node styling together with gradients, shadows, and animations
               entity.nodeType.applyStyle(to: self)
            
           
               layer.masksToBounds = false  // Permits shadow visibility
            
            topAnchor.constraint(equalTo: textField.topAnchor).isActive = true
            bottomAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
            leadingAnchor.constraint(equalTo: textField.leadingAnchor, fixed: -8).isActive = true
            trailingAnchor.constraint(equalTo: textField.trailingAnchor, fixed: 8).isActive = true
        }
        
        non-public func setupGestures() {
            panGesture = UIPanGestureRecognizer(goal: self, motion: #selector(handlePan(_:)))
            self.addGestureRecognizer(panGesture)
        }
        
        func startEditing() {
            textField.becomeFirstResponder()
        }
        
        @objc non-public func handleEditModeChanged(_ notification: Notification) {
            if let userInfo = notification.userInfo, let isInEditMode = userInfo["isInEditMode"] as? Bool {
                self.isInEditMode = isInEditMode
            }
        }
        
      
    
    
    
    
        
      
    
        
        @objc func handlePan(_ sender: UIPanGestureRecognizer) {
            guard let superview = self.superview else { return }
    
            // Calculate the interpretation
            let translation = sender.translation(in: superview)
    
            // Replace the node's heart place
            self.heart = CGPoint(x: self.heart.x + translation.x, y: self.heart.y + translation.y)
            sender.setTranslation(.zero, in: superview)
    
            // Replace the entity's place within the information mannequin
            entity.xPosition = self.body.origin.x
            entity.yPosition = self.body.origin.y
    
          
         
            // Notify the dad or mum view controller to deal with extra updates (e.g., traces)
            var nextResponder: UIResponder? = self
            whereas nextResponder != nil {
                if let scrollViewController = nextResponder as? ScrollViewController {
                    scrollViewController.updateLinePosition(for: self)
                    break
                }
                nextResponder = nextResponder?.subsequent
            }
    
            // Deal with the gesture state for additional updates or actions
            if sender.state == .ended {
                // Optionally carry out any cleanup or extra updates when the gesture ends
                print("✅ Pan gesture ended. Node place: (heart)")
            }
        }
    
        
        func findViewController() -> UIViewController? {
            var nextResponder: UIResponder? = self
            whereas nextResponder != nil {
                if let viewController = nextResponder as? UIViewController {
                    return viewController
                }
                nextResponder = nextResponder?.subsequent
            }
            return nil
        }
        
        non-public func typeChanged(sort: TextFieldType) {
            change sort {
            case .major:
                self.layer.borderColor = UIColor.black.cgColor
            case .secondary:
                self.layer.borderColor = UIColor.white.cgColor
            case .none:
                self.layer.borderColor = UIColor.clear.cgColor
            }
            self.layer.borderWidth = 2
            entity.sort = sort.rawValue
        }
        
        func textFieldDidEndEditing(_ textField: UITextField) {
            entity.textual content = textField.textual content
        }
    
    }

LEAVE A REPLY

Please enter your comment!
Please enter your name here