เจาะลึกการออกแบบการเคลื่อนไหว: การเปลี่ยน iOS ขั้นสูง

เผยแพร่แล้ว: 2020-11-09

ความคิดสร้างสรรค์นำมาซึ่งความปรารถนาอย่างต่อเนื่องที่จะสร้างความประทับใจให้ผู้ใช้ เป็นเวลาหลายศตวรรษมาแล้วที่มนุษย์พยายามใช้วิธีการที่มีอยู่เพื่อสร้างปฏิสัมพันธ์ที่เป็นธรรมชาติที่สุด โดยถือว่าธรรมชาติเป็นตัวอย่างพื้นฐาน

เมื่อค้นพบโลก คนๆ หนึ่งจะมีความรู้สึกอ่อนไหวต่อรายละเอียดเล็กๆ น้อยๆ ของโลกรอบตัวมากขึ้นเรื่อยๆ ซึ่งทำให้พวกเขาสามารถแยกแยะการประดิษฐ์จากสิ่งมีชีวิตตามสัญชาตญาณได้ บรรทัดนี้ไม่ชัดเจนด้วยการพัฒนาเทคโนโลยี ซึ่งซอฟต์แวร์มีเป้าหมายเพื่อสร้างสภาพแวดล้อมที่ผู้ใช้อธิบายประสบการณ์ของเขาในโลกที่สร้างขึ้นโดยธรรมชาติ

สะท้อนธรรมชาติในการออกแบบแอพ

บทความนี้จะแนะนำขั้นตอนการเบลอเส้นขอบโดยใช้ตัวอย่างการเปลี่ยนรูปร่างในแอนิเมชั่นเชิงโต้ตอบขององค์ประกอบในชีวิตประจำวันในแอปพลิเคชัน iOS ส่วนใหญ่ วิธีหนึ่งในการเลียนแบบธรรมชาติคือการเปลี่ยนแปลงตำแหน่งของวัตถุในเวลาต่างๆ ฟังก์ชันที่เป็นแบบอย่างของเวลาแอนิเมชันแสดงไว้ด้านล่าง

เมื่อรวมกับการใช้เวลาอย่างเหมาะสม โดยการเพิ่มการแปลงทางเรขาคณิต เราจะได้รับเอฟเฟกต์จำนวนอนันต์ จากการสาธิตความเป็นไปได้ของการออกแบบและเทคโนโลยีในปัจจุบัน แอปพลิเคชัน Motion Patterns ได้ถูกสร้างขึ้น ซึ่งรวมถึงโซลูชันยอดนิยม ซึ่งพัฒนาโดยบริษัทพัฒนาซอฟต์แวร์ของเรา เนื่องจากฉันไม่ใช่นักเขียน แต่เป็นโปรแกรมเมอร์ และไม่มีอะไรจะพูดได้ดีไปกว่าตัวอย่างสด ฉันไม่มีทางเลือกอื่นนอกจากต้องเชิญคุณเข้าสู่โลกมหัศจรรย์นี้!

ตัวอย่างที่จะค้นพบ

มาดูกันว่าการออกแบบการเคลื่อนไหวสามารถเปลี่ยนการออกแบบที่ล้ำสมัยให้เป็นสิ่งที่พิเศษได้อย่างไร! ในตัวอย่างด้านล่าง ทางด้านซ้ายมีแอปพลิเคชันที่ใช้เฉพาะแอนิเมชั่น iOS พื้นฐาน ในขณะที่ทางด้านขวามีแอปพลิเคชันเวอร์ชันเดียวกันที่มีการปรับปรุงบางอย่าง

เอฟเฟกต์ “ดำน้ำลึก”

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

*จริง ๆ แล้วการคัดลอก / แมปองค์ประกอบข้อมูลในมุมมองชั่วคราวที่มีส่วนร่วมในการเปลี่ยนแปลง ระหว่างจุดเริ่มต้นและจุดสิ้นสุด … แต่ฉันจะอธิบายสิ่งนี้ในบทความนี้ในภายหลัง…

เอฟเฟกต์ “มองทะลุขอบ”

การใช้แอนิเมชั่นมุมมองเลื่อนในการกระทำจะเปลี่ยนรูปภาพใน รูปแบบ 3 มิติสำหรับเอฟเฟกต์ลูกบาศก์ ปัจจัยหลักที่ทำให้เกิดเอฟเฟกต์คือออฟเซ็ตของมุมมองการเลื่อน

เอฟเฟกต์ “เชื่อมต่อจุด”

นี่คือการเปลี่ยนแปลงระหว่างฉากต่างๆ ที่ เปลี่ยนวัตถุขนาดเล็กเป็นทั้งหน้าจอ คอลเลกชันที่ใช้สำหรับวัตถุประสงค์นี้ทำงานพร้อมกัน การเปลี่ยนแปลงบนหน้าจอหนึ่งสอดคล้องกับการเปลี่ยนแปลงในอีกหน้าจอหนึ่ง นอกจากนี้ เมื่อคุณป้อนภาพย่อส่วนในพื้นหลัง เอฟเฟกต์พารัลแลกซ์จะปรากฏขึ้นเมื่อคุณปัดไปมาระหว่างฉากต่างๆ

“เปลี่ยนรูปร่าง” เอฟเฟกต์

แอนิเมชั่นประเภทสุดท้ายเป็นแบบง่ายๆ โดยใช้ไลบรารี Lottie เป็นการใช้งานทั่วไปสำหรับไอคอนแอนิเมชั่น ในกรณีนี้ นี่คือไอคอนบนแถบแท็บ นอกจากนี้ ด้วยการเปลี่ยนแท็บที่เหมาะสม แอนิเมชั่นของการเปลี่ยนแปลงในทิศทางเฉพาะถูกใช้เพื่อเพิ่มเอฟเฟกต์การโต้ตอบให้เข้มข้นขึ้น

เจาะลึก: รูปแบบการออกแบบการเคลื่อนไหวครั้งแรกของเรา

ตอนนี้ได้เวลาเข้าประเด็นแล้ว… เราจำเป็นต้องเจาะลึกลงไปในโครงสร้างของกลไกที่ควบคุมตัวอย่างเหล่านี้

ในบทความนี้ ผมจะแนะนำให้คุณรู้จักกับ รูปแบบการออกแบบการเคลื่อนไหวรูปแบบ แรก ซึ่งเราตั้งชื่อว่า 'Dive Deeper' พร้อมคำอธิบายที่เป็นนามธรรมในการใช้งาน โดยไม่ต้องลงรายละเอียดเฉพาะเจาะจง เราวางแผนที่จะสร้างรหัสที่แน่นอนและพื้นที่เก็บข้อมูลทั้งหมดให้กับทุกคนในอนาคตโดยไม่มีข้อจำกัดใดๆ

สถาปัตยกรรมของโปรเจ็กต์และรูปแบบการออกแบบการเขียนโปรแกรมที่เข้มงวดนั้นไม่ใช่สิ่งสำคัญในตอนนี้—เราเน้นที่แอนิเมชันและทรานซิชัน

ในบทความนี้ เราจะใช้คุณลักษณะสองชุดที่มีให้เพื่อจัดการมุมมองระหว่างการเปลี่ยนฉาก ดังนั้น ฉันอยากจะชี้ให้เห็นว่าบทความนี้มีไว้สำหรับผู้ที่ค่อนข้างคุ้นเคยกับ UIKit และไวยากรณ์ของ Swift

https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning

https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition

https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html

ครั้งแรก: โครงสร้าง

สำหรับเวอร์ชันพื้นฐานที่ใช้โซลูชันที่กำหนด จะต้องใช้คลาสตัวช่วยหลายคลาส รับผิดชอบในการให้ข้อมูลที่จำเป็นเกี่ยวกับมุมมองที่เกี่ยวข้องกับการเปลี่ยนแปลง และควบคุมการเปลี่ยนแปลงเองและการโต้ตอบ

การเปลี่ยนแปลงใน Swift

คลาสพื้นฐานที่รับผิดชอบในการจัดการการเปลี่ยนแปลง พร็อกซี จะเป็น TransitionAnimation โดยจะตัดสินว่าการเปลี่ยนแปลงจะเกิดขึ้นด้วยวิธีใดและครอบคลุมฟังก์ชันมาตรฐานที่จำเป็นสำหรับการดำเนินการที่ทีม Apple ให้ไว้

 /// นี่คือคลาสพื้นฐานสำหรับการเปลี่ยนซึ่งมีพฤติกรรมที่แตกต่างกันในการนำเสนอและการยกเลิกในช่วงเวลาที่กำหนด
TransitionAnimator คลาสเปิด: NSObject, UIViewControllerAnimatedTransitioning {
    
    /// ระบุว่าเป็นการนำเสนอหรือยกเลิกการเปลี่ยนแปลง
    นำเสนอ: Bool = true
    
    /// ช่วงเวลาที่มีการเปลี่ยนแปลงทั้งหมดเกิดขึ้น
    ระยะเวลาปล่อยส่วนตัว: TimeInterval
    
    /// ตัวเริ่มต้นเริ่มต้นของแอนิเมเตอร์การเปลี่ยนแปลงพร้อมค่าเริ่มต้น
    /// - ระยะเวลาพารามิเตอร์: ช่วงเวลาที่เกิดการเปลี่ยนแปลงทั้งหมด
    /// - การนำเสนอพารามิเตอร์: ตัวบ่งชี้ว่ากำลังนำเสนอหรือยกเลิกการเปลี่ยนแปลง
    การเริ่มต้นสาธารณะ (duration: TimeInterval = 0.5 นำเสนอ: Bool = true) {
        self.duration = ระยะเวลา
        self.presenting = นำเสนอ
        super.init()
    }
    
    /// กำหนดระยะเวลาของการเปลี่ยนแปลง
    /// - บริบทการเปลี่ยนผ่านพารามิเตอร์: บริบทของการเปลี่ยนแปลงปัจจุบัน
    /// - ส่งกลับ: ระยะเวลาที่ระบุเมื่อเริ่มต้นแอนิเมเตอร์
    การเปลี่ยนแปลงของ func สาธารณะ Duration (โดยใช้ TransitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        คืนตัวเอง.duration
    }
    
    /// หัวใจของแอนิเมชั่นการเปลี่ยนแปลง ในการเปลี่ยนฟังก์ชันนี้เกิดขึ้น
    /// - สำคัญ: การแทนที่ฟังก์ชันนี้ในประเภททรานสิชันที่เป็นรูปธรรมมากขึ้นเป็นสิ่งสำคัญในการแสดงแอนิเมชั่น
    /// - บริบทการเปลี่ยนผ่านพารามิเตอร์: บริบทของการเปลี่ยนแปลง
    animateTransition func สาธารณะ (โดยใช้ TransitionContext: UIViewControllerContextTransitioning) { }
    
}

ตาม TransitionAnimator เราสร้างไฟล์ TransformTransition ซึ่งงานที่จะทำการเปลี่ยนแปลงเฉพาะ การเปลี่ยนแปลงด้วยการแปลง (การเลี้ยงดู)

 /// การดำเนินการเปลี่ยนการแปลง
TransformTransition คลาสเปิด: TransitionAnimator {
    
    /// ดูโมเดลซึ่งมีข้อกำหนดทั้งหมดของการเปลี่ยนแปลงที่จำเป็นทั้งหมด
    var viewModel ส่วนตัว: TransformViewModel
    
    /// ตัวเริ่มต้นเริ่มต้นของการเปลี่ยนการแปลง
    /// - พารามิเตอร์ viewModel: ดูแบบจำลองของการเปลี่ยนแปลงการแปลง
    /// - ระยะเวลาพารามิเตอร์: ระยะเวลาของการเปลี่ยนแปลง
    init (viewModel: TransformViewModel ระยะเวลา: TimeInterval) {
        self.viewModel = ดูโมเดล
        super.init(duration: duration, presenting: viewModel.presenting)
    }

องค์ประกอบของคลาส TransformTransition รวมถึง TransformViewModel ซึ่งตามชื่อแนะนำ แจ้งกลไกของโมเดลมุมมองที่การเปลี่ยนแปลงนี้จะนำไปใช้กับ

 /// ดูแบบจำลองการเปลี่ยนรูปซึ่งมีข้อมูลพื้นฐานเกี่ยวกับมัน
คลาสสุดท้าย TransformViewModel {
    
    /// ระบุว่าการเปลี่ยนการแปลงแสดงหรือปิดมุมมอง
    ให้นำเสนอ: Bool
    /// อาร์เรย์ของโมเดลที่มีข้อกำหนดเกี่ยวกับการแปลงสำหรับแต่ละมุมมอง
    ให้โมเดล: [TransformModel]
    
    /// ตัวเริ่มต้นเริ่มต้นของโมเดลมุมมองการแปลง
    /// - การนำเสนอพารามิเตอร์: ระบุว่ากำลังนำเสนอหรือปิดการเปลี่ยนการแปลง
    /// - โมเดลพารามิเตอร์: อาร์เรย์ของโมเดลที่มีข้อกำหนดเกี่ยวกับการแปลงสำหรับแต่ละมุมมอง
    init (นำเสนอ: Bool รุ่น: [TransformModel]) {
        self.presenting = นำเสนอ
        self.models = โมเดล
    }
    
}

โมเดลการแปลงเป็นคลาสเสริมที่อธิบายองค์ประกอบเฉพาะของมุมมองที่เกี่ยวข้องกับการเปลี่ยนแปลงที่อยู่ในพาเรนต์ ซึ่งมักจะเป็นมุมมองของคอนโทรลเลอร์ที่สามารถแปลงได้

ในกรณีของการเปลี่ยนแปลง ถือเป็นขั้นตอนที่จำเป็นเนื่องจากการเปลี่ยนแปลงนี้ประกอบด้วยการดำเนินการตามความคิดเห็นเฉพาะระหว่างรัฐที่กำหนด

ประการที่สอง: การนำไปใช้

เราขยายโมเดลการดูที่เราเริ่มการเปลี่ยนแปลงด้วย Transformable ซึ่งบังคับให้เราใช้ฟังก์ชันที่จะเตรียมองค์ประกอบที่จำเป็นทั้งหมด ขนาดของฟังก์ชันนี้สามารถขยายได้เร็วมาก ดังนั้น เราขอแนะนำให้คุณแยกเป็นส่วนย่อยๆ เช่น ต่อองค์ประกอบ

 /// โปรโตคอลสำหรับคลาสที่ต้องการเปลี่ยนการแปลง
โปรโตคอลที่แปลงได้: ViewModel {
    
    /// จัดทำแบบจำลองความคิดเห็นที่เกี่ยวข้องกับการเปลี่ยนแปลง
    /// - พารามิเตอร์ fromView: มุมมองจากการเปลี่ยนภาพเริ่มต้น
    /// - พารามิเตอร์เพื่อดู: มุมมองที่จะเปลี่ยน
    /// - การนำเสนอพารามิเตอร์: ระบุว่ากำลังนำเสนอหรือยกเลิก
    /// - ส่งกลับ: อาร์เรย์ของโครงสร้างที่เก็บข้อมูลที่จำเป็นทั้งหมดพร้อมที่จะแปลงการเปลี่ยนแปลงสำหรับแต่ละมุมมอง
    func prepareTransitionModels (จากมุมมอง: UIView, toView: UIView, กำลังนำเสนอ: Bool) -> [TransformModel]
    
}

สมมติฐานไม่ได้บอกว่าจะค้นหาข้อมูลของความคิดเห็นที่มีส่วนร่วมในการเปลี่ยนแปลงได้อย่างไร ในตัวอย่างของฉัน ฉันใช้แท็กที่แสดงมุมมองที่กำหนด คุณมีอิสระในการใช้งานส่วนนี้

โมเดลของการแปลงมุมมองเฉพาะ (TransformModel) เป็นโมเดลที่เล็กที่สุดในรายการทั้งหมด ประกอบด้วยข้อมูลการแปลงคีย์ เช่น มุมมองเริ่มต้น มุมมองการเปลี่ยน เฟรมเริ่มต้น เฟรมสิ้นสุด ศูนย์เริ่มต้น ศูนย์สิ้นสุด ภาพเคลื่อนไหวพร้อมกัน และการดำเนินการสิ้นสุด พารามิเตอร์ส่วนใหญ่ไม่จำเป็นต้องใช้ระหว่างการแปลง ดังนั้นจึงมีค่าเริ่มต้นของตัวเอง เพื่อผลลัพธ์ที่น้อยที่สุดก็เพียงพอแล้วที่จะใช้เฉพาะสิ่งที่จำเป็นเท่านั้น

 /// ตัวเริ่มต้นเริ่มต้นของโมเดลการแปลงที่มีค่าเริ่มต้น
    /// - พารามิเตอร์ initialView: มุมมองจากจุดเริ่มต้นของการเปลี่ยน
    /// - พารามิเตอร์ phantomView: มุมมองที่แสดงระหว่างการเปลี่ยนการแปลง
    /// - พารามิเตอร์ initialFrame: เฟรมของมุมมองที่เริ่มการแปลงการเปลี่ยนแปลง
    /// - พารามิเตอร์ finalFrame: เฟรมของมุมมองซึ่งจะแสดงเมื่อสิ้นสุดการเปลี่ยนการแปลง
    /// - พารามิเตอร์ initialCenter: จำเป็นเมื่อจุดศูนย์กลางของมุมมองเริ่มต้นแตกต่างจากจุดศูนย์กลางของมุมมองเริ่มต้น
    /// - Parameter finalCenter: จำเป็นเมื่อจุดศูนย์กลางของมุมมองสุดท้ายแตกต่างจากจุดศูนย์กลางของมุมมองสุดท้าย
    /// - Parameter ParallelAnimation: ภาพเคลื่อนไหวเพิ่มเติมของมุมมองที่ดำเนินการระหว่างการเปลี่ยนรูปแบบ
    /// - พารามิเตอร์เสร็จสมบูรณ์: บล็อกของโค้ดที่ทริกเกอร์หลังจากการเปลี่ยนการแปลง
    /// - หมายเหตุ: ต้องใช้เฉพาะมุมมองเริ่มต้นเพื่อดำเนินการเปลี่ยนรูปแบบที่เรียบง่ายที่สุด
    init (initialView: UIView,
         phantomView: UIView = UIView (),
         initialFrame: CGRect = CGRect (),
         เฟรมสุดท้าย: CGRect = CGRect (),
         initialCenter: CGPoint? = ไม่มี
         finalCenter: CGPoint? = ไม่มี
         ParallelAnimation: (() -> โมฆะ)? = ไม่มี
         เสร็จสิ้น: (() -> โมฆะ)? = ไม่มี) {
        self.initialView = initialView
        self.phantomView = phantomView
        self.initialFrame = initialFrame
        self.finalFrame = เฟรมสุดท้าย
        self.parallelAnimation = แอนิเมชั่นคู่ขนาน
        self.completion = เสร็จสิ้น
        self.initialCenter = initialCenter ?? CGPoint(x: initialFrame.midX, y: initialFrame.midY)
        self.finalCenter = finalCenter ?? CGPoint(x: finalFrame.midX, y: finalFrame.midY)
    }

ความสนใจของคุณอาจถูกจับโดย Phantom View นี่คือช่วงเวลาที่ฉันจะอธิบายเวิร์กโฟลว์สำหรับการเปลี่ยน iOS ในรูปแบบที่สั้นที่สุด…

เวิร์กโฟลว์สำหรับการเปลี่ยน iOS

เมื่อผู้ใช้ต้องการย้ายไปยังฉากถัดไป iOS จะเตรียมตัวควบคุมเฉพาะโดยการคัดลอกตัวควบคุมเริ่มต้น (สีน้ำเงิน) และเป้าหมาย (สีเขียว) ไปยังหน่วยความจำ ถัดไป บริบทการเปลี่ยนแปลงจะถูกสร้างขึ้นผ่านผู้ประสานงานการเปลี่ยนแปลงซึ่งมีคอนเทนเนอร์ มุมมอง 'โง่' ที่ไม่มีฟังก์ชันพิเศษใดๆ นอกเหนือจากการจำลองมุมมองการเปลี่ยนแปลงระหว่างสองฉาก

หลักการสำคัญของการทำงานกับการเปลี่ยนคือการไม่เพิ่มมุมมองจริงใดๆ ให้กับบริบทการเปลี่ยนแปลง เนื่องจากเมื่อสิ้นสุดการเปลี่ยนแปลง บริบททั้งหมดจะได้รับการจัดสรรคืนพร้อมกับมุมมองที่เพิ่มลงในคอนเทนเนอร์ นี่คือมุมมองที่มีอยู่เฉพาะระหว่างการเปลี่ยนแปลงและจะถูกลบออก

ดังนั้น การใช้มุมมองแฝงที่จำลองมุมมองจริงจึงเป็นวิธีแก้ปัญหาที่สำคัญสำหรับการเปลี่ยนแปลงนี้

ในกรณีนี้ เรามีการเปลี่ยนภาพที่เปลี่ยนมุมมองหนึ่งเป็นอีกมุมมองหนึ่งโดยการเปลี่ยนรูปร่างและขนาด ในการทำเช่นนี้ เมื่อเริ่มต้นการเปลี่ยนแปลง ฉันสร้าง PhantomView ขององค์ประกอบที่กำหนดและเพิ่มลงในคอนเทนเนอร์ FadeView เป็นมุมมองเสริมเพื่อเพิ่มความนุ่มนวลให้กับการเปลี่ยนแปลงโดยรวม

 /// หัวใจของทรานส์ฟอร์เมชั่น ที่ซึ่งทรานสิชั่นดำเนินการ แทนที่ `TransitionAnimator.animateTransition(...)`
    /// - บริบทการเปลี่ยนผ่านพารามิเตอร์: บริบทของการเปลี่ยนการแปลงปัจจุบัน
    แทนที่ open func animateTransition (โดยใช้ TransitionContext: UIViewControllerContextTransitioning) {
        guard ให้ toViewController = TransitContext.view(forKey: .to)
            ให้ fromViewController = TransitContext.view (forKey: .from) อื่น {
                ส่งคืน Log.unexpectedState()
        }
        ให้ containerView = TransitContext.containerView
        ให้ระยะเวลา = เปลี่ยนระยะเวลา (โดยใช้: เปลี่ยนบริบท)
        ให้ fadeView = toViewController.makeFadeView(ความทึบ: (!นำเสนอ).cgFloatValue)
        ให้รุ่น = viewModel.models
        ให้ presentView = นำเสนอ ? toViewController : fromViewController
        
        model.forEach { $0.initialView.isHidden = true }
        PresentView.isHidden = true
        containerView.addSubview (toViewController)
        ถ้านำเสนอ {
            containerView.insertSubview (fadeView, ด้านล่าง Subview: toViewController)
        } อื่น {
            containerView.addSubview (fadeView)
        }
        containerView.addSubviews (viewModel.models.map { $0.phantomView })

ในขั้นตอนต่อไป ฉันแปลงเป็นรูปร่างเป้าหมายผ่านการแปลง และขึ้นอยู่กับว่าเป็นการนำเสนอหรือการเรียกคืน มันดำเนินการเพิ่มเติมเพื่อล้างมุมมองเฉพาะ - นี่คือสูตรทั้งหมดสำหรับการเปลี่ยนแปลงนี้

 ให้ภาพเคลื่อนไหว: () -> โมฆะ = { [ตัวเองอ่อนแอ] ใน
            ยามปล่อยให้ตัวเอง = ตัวเองอื่น { กลับ Log.unexpectedState () }
            fadeView.alpha = self.presenting.cgFloatValue
            model.forEach {
                ให้ center = self.presenting ? $0.finalCenter : $0.initialCenter
                ให้ transform = self.presenting ? $0.presentTransform : $0.dismissTransform
                $0.phantomView.setTransformAndCenter(แปลง, ศูนย์กลาง)
            }
            model.compactMap { $0.parallelAnimation }.forEach { $0() }
        }
        
        ปล่อยให้เสร็จสิ้น: (บูล) -> โมฆะ = { _ in
            TransitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            PresentView.isHidden = false
            models.compactMap { $0 เสร็จสมบูรณ์ }.forEach { $0() }
            model.forEach { $0.initialView.isHidden = false }
            ถ้า !self.presenting && transitionContext.transitionWasCancelled {
                toViewController.removeFromSuperview()
                fadeView.removeFromSuperview()
            }
        }
        
        UIView.animate(ด้วยDuration: Duration,
                       ล่าช้า: 0
                       ใช้SpringWithDamping: 1,
                       เริ่มต้นสปริงความเร็ว: 0.5,
                       ตัวเลือก: .curveEaseOut,
                       แอนิเมชั่น: แอนิเมชั่น,
                       เสร็จสิ้น: เสร็จสิ้น)

ที่สาม: ส่วนผสมพิเศษ

หลังจากรวบรวมฟังก์ชัน คลาส และโปรโตคอลทั้งหมดแล้ว ผลลัพธ์ควรมีลักษณะดังนี้:

องค์ประกอบสุดท้ายของการเปลี่ยนแปลงของเราคือการโต้ตอบอย่างเต็มที่ เพื่อจุดประสงค์นี้ เราจะใช้ Pan Gesture ที่เพิ่มในมุมมองคอนโทรลเลอร์ TransitionInteractor...

 /// ผู้ไกล่เกลี่ยในการจัดการการเปลี่ยนแปลงแบบโต้ตอบ
TransitionInteractor คลาสสุดท้าย: UIPercentDrivenInteractiveTransition {
    
    /// ระบุว่าการเปลี่ยนแปลงได้เริ่มต้นขึ้นแล้ว
    var hasStarted = false
    /// ระบุว่าการเปลี่ยนแปลงควรเสร็จสิ้นหรือไม่
    var shouldFinish = false

}

… ซึ่งเราเริ่มต้นในตัวควบคุมด้วย

 /// จัดการท่าทางการเลื่อนบนรายการมุมมองคอลเลกชัน และจัดการการเปลี่ยนแปลง
    @objc func handlePanGesture (_ ตัวรับรู้ท่าทางสัมผัส: UIPanGestureRecognizer) {
        ให้เปอร์เซ็นต์เกณฑ์: CGFloat = 0.1
        ให้การแปล = GestureRecognizer.translation (ใน: มุมมอง)
        ให้ verticalMovement = translation.y / view.bounds.height
        ให้ upwardMovement = fminf(Float(verticalMovement), 0.0)
        ให้ upwardMovementPercent = fminf(abs(upwardMovement), 0.9)
        ให้คืบหน้า = CGFloat(upwardMovementPercent)
        guard ให้ผู้โต้ตอบ = การโต้ตอบตัวควบคุมอื่น { กลับ }
        สลับ GestureRecognizer.state {
        กรณี .begin:
            โต้ตอบ.hasStarted = true
            ให้ tapPosition = GestureRecognizer.location (ใน: collectionView)
            showDetailViewControllerFrom(ตำแหน่ง: tapPosition)
        กรณี .change:
            โต้ตอบ.shouldFinish = ความคืบหน้า > เปอร์เซ็นต์เกณฑ์
            reactor.update (ความคืบหน้า)
        กรณี .cancelled:
            ตัวโต้ตอบ.hasStarted = false
            ตัวโต้ตอบ. ยกเลิก ()
        กรณี .ended:
            ตัวโต้ตอบ.hasStarted = false
            โต้ตอบ.ควรเสร็จสิ้น
                ? ตัวโต้ตอบเสร็จสิ้น ()
                : ตัวโต้ตอบ. ยกเลิก ()
        ค่าเริ่มต้น:
            หยุดพัก
        }
    }

การโต้ตอบที่พร้อมควรเป็นดังนี้:

หากทุกอย่างเป็นไปตามแผน แอปพลิเคชันของเราจะได้รับประโยชน์มากขึ้นในสายตาของผู้ใช้

ค้นพบเพียงยอดภูเขาน้ำแข็ง

คราวหน้า ผมจะอธิบายการใช้งานประเด็นที่เกี่ยวข้องกับการออกแบบการเคลื่อนไหวในฉบับต่อไปนี้

แอปพลิเคชัน การออกแบบ และซอร์สโค้ดเป็นทรัพย์สินของ Miquido และสร้างขึ้นด้วยความหลงใหลโดยนักออกแบบและโปรแกรมเมอร์ที่มีพรสวรรค์ในการใช้งาน ซึ่งเราไม่รับผิดชอบในการใช้งานของเรา ซอร์สโค้ดโดยละเอียดจะพร้อมใช้งานในอนาคตผ่านบัญชี github ของเรา เราขอเชิญคุณติดตามเรา!

ขอบคุณสำหรับความสนใจของคุณ แล้วพบกันใหม่!