เหตุใดวิธีการวาดของ CALayer แบบกำหนดเองจึงไม่ถูกเรียกก่อนหน้านี้

ปัญหา

ฉันทริกเกอร์ภาพเคลื่อนไหวแบบสุ่มบน UIViews ที่เลือกโดยใช้ตัวจับเวลา แอนิเมชั่นทั้งหมดทำงานตามที่ตั้งใจไว้ แต่เป็นอันหนึ่งที่มีการเรียกวิธีการวาดของ CALayer หลังจากออกจากตัวเลือกตัวจับเวลา

คำอธิบายโดยละเอียด

เพื่อความชัดเจนและง่ายขึ้น ผมขอแผนผังการดำเนินการที่ทำไปแล้ว

วนรอบ

แอนิเมชั่นทั้งหมดที่ฉันสร้างจนถึงตอนนี้ทำงานได้ตามที่ตั้งใจไว้: เป็นการผสมผสานระหว่าง CABasicAnimations บนมุมมองย่อย/เลเยอร์ย่อยที่มีอยู่ หรืออันใหม่ที่เพิ่มเข้าไปในลำดับชั้นการดูที่เลือก พวกมันถูกเข้ารหัสเป็นส่วนขยาย UIView เพื่อให้สามารถเรียกพวกมันในมุมมองใดก็ได้ โดยไม่คำนึงถึงมุมมองหรือตัวควบคุมมุมมองที่มีมุมมองนั้นอยู่ ก็ใช้งานได้ทั้งหมดยกเว้นมุมมองเดียว

ฉันได้สร้างคลาส CALayer แบบกำหนดเองซึ่งประกอบด้วยรูปแบบการวาดบน CALayer ในส่วนขยายของคลาสที่กำหนดเองนี้ มีวิธีทำให้รูปแบบเหล่านั้นเคลื่อนไหว (ดูโค้ดต่อไปนี้) โดยรวมแล้ว เมื่อฉันไปถึงขั้นตอน/วิธีการ animate selected view และเรียกใช้ภาพเคลื่อนไหวนี้ นี่คือสิ่งที่ควรเกิดขึ้น:

  • วิธีการชื่อ animatePattern เรียกว่า
  • เมธอดนี้จะเพิ่มคลาส CALayer แบบกำหนดเองให้กับมุมมองที่เลือก จากนั้นเรียกส่วนขยายภาพเคลื่อนไหวของเลเยอร์นี้

ปัญหา: หากภาพเคลื่อนไหวอื่นๆ ทั้งหมด การวาดภาพทั้งหมดจะดำเนินการก่อนออกจากขั้นตอน/วิธีการ animate selected view ในกรณีดังกล่าว วิธีการวาดคลาส CALayer แบบกำหนดเองจะเรียกว่า หลัง ทางออกของเมธอด performAnimation ซึ่งจะส่งผลให้ภาพเคลื่อนไหวเสียหาย

ฉันควรเพิ่มว่าฉันได้ลองใช้แอนิเมชั่นคลาส CALayer ที่กำหนดเองในสนามเด็กเล่นที่แยกจากกันและง่ายขึ้นและทำงานได้ดี (ฉันเพิ่มเลเยอร์ที่กำหนดเองให้กับ UIView ในวิธี viewDidLoad ของ UIViewController จากนั้นฉันจะเรียกภาพเคลื่อนไหวของเลเยอร์ในวิธี viewDidAppear ของ UIViewController)

รหัส

  • วิธีการที่ถูกเรียกโดย animate selected view step/method:

    func animatePattern(for duration: TimeInterval = 1) {
    
    let patternLayer        = PatternLayer(effectType: .dark)
    patternLayer.frame      = self.bounds
    self.layer.addSublayer(patternLayer)
    patternLayer.play(for: duration)
    
    }
    

(โปรดทราบว่าวิธีนี้อยู่ในส่วนขยาย UIView ดังนั้น self ที่นี่จึงแสดงถึง UIView ที่มีการเรียกภาพเคลื่อนไหว)

  • คลาส CALayer แบบกำหนดเองที่เรียบง่าย:

    override var bounds: CGRect {
        didSet {
            self.setNeedsDisplay()
        }
    
    
    // Initializers
    override init() {
        super.init()
        self.setNeedsDisplay()
    }
    
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    init(effectType: EffectType){
        super.init()
        // sets various properties
        self.setNeedsDisplay()
    }
    
    
    // Drawing
    override func draw(in ctx: CGContext) {
        super.draw(in: ctx)
        guard self.frame        != CGRect.zero else { return }
    
        self.masksToBounds      = true
    
        // do the drawings
    }
    
  • ส่วนขยายภาพเคลื่อนไหวของคลาส CALayer ที่กำหนดเอง:

     func play(for duration: Double, removeAfterCompletion: RemoveAfterCompletion = .no) {
    
        guard self.bounds != CGRect.zero else {
            print("Cannot animate nil layer")
            return }
    
        CATransaction.begin()
    
        CATransaction.setCompletionBlock {
    
            if removeAfterCompletion.removeStatus {
                if case RemoveAfterCompletion.yes = removeAfterCompletion { self.fadeOutDuration = 0.0 }
                self.fadeToDisappear(duration: &self.fadeOutDuration)
            }
        }
    
    
        // perform animations
    
        CATransaction.commit()
    }
    

ความพยายามจนถึงขณะนี้

ฉันได้พยายามบังคับวาดเลเยอร์โดยการแทรก setNeedsDisplay / setNeedsLayout ที่ตำแหน่งต่างๆ ในโค้ด แต่มันไม่ทำงาน: การดีบักเมธอด draw ของคลาส CALayer แบบกำหนดเองนั้นเข้าถึงอย่างต่อเนื่องหลังจากออกจากเมธอด performAnimation ในขณะที่มันควรจะถูกเรียกเมื่อ เฟรมของเลเยอร์ได้รับการแก้ไขในวิธี animatePattern ฉันต้องพลาดบางสิ่งบางอย่างที่ค่อนข้างชัดเจน แต่ตอนนี้ฉันกำลังวิ่งวนเป็นวงกลม และฉันจะซาบซึ้งกับสายตาคู่ใหม่ที่ได้จ้องมองมัน

ขอขอบคุณล่วงหน้าที่สละเวลาพิจารณาปัญหานี้! ดีที่สุด,


person Chris    schedule 11.02.2019    source แหล่งที่มา
comment
สำหรับเลเยอร์การวาด คุณสามารถยกตัวอย่างแอนิเมชั่นที่คุณกำลังแสดงโดยที่คุณมี //แสดงแอนิเมชั่น   -  person agibson007    schedule 12.02.2019


คำตอบ (2)


คุณแทนที่การวาด (_ บริบท) เนื่องจาก UIView สามารถเป็นตัวแทนของ CALayer ได้

 UIView: {

    var patternLayer :  PatternLayer

     func animatePattern(for duration: TimeInterval = 1) {

      patternLayer      = PatternLayer(effectType: .dark)
       patternLayer.frame      = self.bounds
       self.layer.addSublayer(patternLayer)
     }

      func draw(_ layer: CALayer, in ctx: CGContext){
        layer.draw(ctx)
         if  NECESSARY { patternLayer.play(for: duration)}

        }

      }
person E.Coms    schedule 11.02.2019
comment
ขอขอบคุณที่สละเวลาแนะนำวิธีแก้ปัญหา! เท่าที่ฉันเข้าใจความคิดของคุณ ฉันจะแทนที่ draw(_context:) ในมุมมองที่ขึ้นอยู่กับภาพเคลื่อนไหว หากเป็นเช่นนั้น วิธีแก้ไขปัญหานั้นจะไม่สามารถทำงานได้เนื่องจากภาพเคลื่อนไหวจะนำไปใช้กับการเลือกมุมมองแบบสุ่ม ไม่ใช่มุมมองใดมุมมองหนึ่งโดยเฉพาะ เมื่อใดก็ตามที่ตัวจับเวลาถึงเวลาที่กำหนด ตัวจับเวลาจะทำให้เกิดภาพเคลื่อนไหวในบางมุมมอง ซึ่งในนั้นคือ patternLayer - person Chris; 11.02.2019
comment
นั่นเป็นสิ่งจำเป็นที่มาจาก แนวคิดคือการรับประกันการเรียกแอนิเมชั่นหลังจากการวาดเลเยอร์ด้วยเงื่อนไขบางประการ displayIfNeeded() ก็ดีเช่นกัน - person E.Coms; 11.02.2019
comment
ขอขอบคุณอีกครั้ง :) แต่ฉันอาจไม่เข้าใจความคิดของคุณอย่างถ่องแท้... คุณแนะนำให้เพิ่มวิธีนี้ในส่วนขยาย UIView หรือไม่ นอกจากนี้ ภาพเคลื่อนไหวนี้อาจไม่ใช่ภาพเคลื่อนไหวที่ถูกเลือกให้เรียกใช้ตั้งแต่แรก (จะมีการสุ่มเลือกมุมมองและภาพเคลื่อนไหวที่จะนำไปใช้) - person Chris; 11.02.2019
comment
ไม่ ฉันหมายถึงคำตอบของคุณ patternLayer.displayIfNeeded() อยู่ที่นี่ - person E.Coms; 11.02.2019

บ่อยครั้งเป็นเช่นนี้ เมื่อคุณใช้เวลาเขียนปัญหา แนวคิดใหม่ๆ ก็เริ่มผุดขึ้นมา อันที่จริง ฉันจำได้ว่า self.setNeedsDisplay() แจ้งให้ระบบทราบว่าจำเป็นต้องวาดเลเยอร์ (ใหม่) แต่ไม่ได้ (ใหม่) วาดในครั้งเดียว โดยจะเสร็จสิ้นในระหว่างการรีเฟรชครั้งถัดไป ดูเหมือนว่าการรีเฟรชจะเกิดขึ้นหลังจากสิ้นสุดรอบสำหรับภาพเคลื่อนไหวนั้น ๆ เพื่อที่จะเอาชนะปัญหานี้ ก่อนอื่นฉันได้เพิ่มการเรียกเมธอด display ทันทีหลังจากตั้งค่าขอบเขตของ patternLayer และมันใช้งานได้ แต่เมื่อได้รับความคิดเห็น @Matt ฉันจึงเปลี่ยนวิธีแก้ปัญหาด้วยการเรียกเมธอด displayIfNeeded แทน มันได้ผลเช่นกัน

func animatePattern(for duration: TimeInterval = 1) {

let patternLayer        = PatternLayer(effectType: .dark)
patternLayer.frame      = self.bounds
self.layer.addSublayer(patternLayer)

// asks the system to draw the layer now
patternLayer.displayIfNeeded()

patternLayer.play(for: duration)

}

ขอย้ำอีกครั้งว่า หากใครคิดวิธีแก้ปัญหา/คำอธิบายที่สวยงามกว่านี้อีก โปรดอย่าละเว้นที่จะแบ่งปัน!

person Chris    schedule 11.02.2019
comment
ประโยคแรกในเอกสารสำหรับ display: “อย่าเรียกวิธีนี้โดยตรง” - person matt; 11.02.2019
comment
ฉันไม่ได้เรียกวิธีการแสดงของ CALayer แต่เป็นวิธีการแสดงผลของคลาสย่อย แต่ฉันอยากได้ยินความคิดของคุณที่จะเอาชนะปัญหาเป็นอย่างอื่น :) - person Chris; 11.02.2019
comment
ฉันไม่ได้อ่านคำถามของคุณอย่างลึกซึ้ง แต่ถ้าฉันเข้าใจถูกต้อง วิธีที่ฉันจัดการกับเรื่องประเภทนี้คือการหน่วงเวลาเล็กน้อยเพื่อให้ runloop เสร็จสิ้น - person matt; 11.02.2019
comment
ฉันเพิ่งแนะนำ DispatchQueue.main.asyncAfter สั้น ๆ ลงในลำดับเหตุการณ์ - person matt; 11.02.2019