เจาะลึกการออกแบบการเคลื่อนไหว: การเปลี่ยน 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
ครั้งแรก: โครงสร้าง
สำหรับเวอร์ชันพื้นฐานที่ใช้โซลูชันที่กำหนด จะต้องใช้คลาสตัวช่วยหลายคลาส รับผิดชอบในการให้ข้อมูลที่จำเป็นเกี่ยวกับมุมมองที่เกี่ยวข้องกับการเปลี่ยนแปลง และควบคุมการเปลี่ยนแปลงเองและการโต้ตอบ

คลาสพื้นฐานที่รับผิดชอบในการจัดการการเปลี่ยนแปลง พร็อกซี จะเป็น 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 จะเตรียมตัวควบคุมเฉพาะโดยการคัดลอกตัวควบคุมเริ่มต้น (สีน้ำเงิน) และเป้าหมาย (สีเขียว) ไปยังหน่วยความจำ ถัดไป บริบทการเปลี่ยนแปลงจะถูกสร้างขึ้นผ่านผู้ประสานงานการเปลี่ยนแปลงซึ่งมีคอนเทนเนอร์ มุมมอง 'โง่' ที่ไม่มีฟังก์ชันพิเศษใดๆ นอกเหนือจากการจำลองมุมมองการเปลี่ยนแปลงระหว่างสองฉาก
หลักการสำคัญของการทำงานกับการเปลี่ยนคือการไม่เพิ่มมุมมองจริงใดๆ ให้กับบริบทการเปลี่ยนแปลง เนื่องจากเมื่อสิ้นสุดการเปลี่ยนแปลง บริบททั้งหมดจะได้รับการจัดสรรคืนพร้อมกับมุมมองที่เพิ่มลงในคอนเทนเนอร์ นี่คือมุมมองที่มีอยู่เฉพาะระหว่างการเปลี่ยนแปลงและจะถูกลบออก
ดังนั้น การใช้มุมมองแฝงที่จำลองมุมมองจริงจึงเป็นวิธีแก้ปัญหาที่สำคัญสำหรับการเปลี่ยนแปลงนี้

ในกรณีนี้ เรามีการเปลี่ยนภาพที่เปลี่ยนมุมมองหนึ่งเป็นอีกมุมมองหนึ่งโดยการเปลี่ยนรูปร่างและขนาด ในการทำเช่นนี้ เมื่อเริ่มต้นการเปลี่ยนแปลง ฉันสร้าง 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 ของเรา เราขอเชิญคุณติดตามเรา!
ขอบคุณสำหรับความสนใจของคุณ แล้วพบกันใหม่!