
ใช้การปรับแต่งเลย์เอาต์เพื่อควบคุมลำดับชั้นการแสดงผลและการจัดวางหน้าจอสำหรับทั้งมุมมองที่ Google มีให้และมุมมองธุรกิจที่กำหนดเองของคุณเอง
แทนที่จะเพิ่มมุมมองที่กำหนดเองลงในช่องเลย์เอาต์ที่กำหนดไว้ล่วงหน้า ให้ใช้ตัวแทนเลย์เอาต์เพื่อจัดการเลย์เอาต์สำหรับทั้งหน้าจอ ในระหว่างการเปลี่ยนสถานะการนำทาง ตัวแทนเลย์เอาต์จะได้รับคอมโพเนนต์ที่ Google มีให้ เช่น การ์ดเลี้ยว การ์ด ETA และปุ่มต่างๆ วางองค์ประกอบเหล่านี้ไว้ข้างเนื้อหาที่กำหนดเองโดยใช้ข้อจำกัดของ Auto Layout มาตรฐานของ iOS
ใช้เฟรมเวิร์กนี้เพื่อจัดตำแหน่งข้อมูลทางธุรกิจที่กำหนดเอง เช่น สถานะการสั่งซื้อหรือวิธีการรับสินค้า บนหน้าจออย่างแม่นยำพร้อมทั้งป้องกันไม่ให้มุมมองซ้อนทับกัน
วิธีการทำงานของการปรับแต่งเลย์เอาต์
การปรับแต่งเลย์เอาต์ใช้รูปแบบการออกแบบการมอบสิทธิ์ แทนที่จะวาดหรือวางตำแหน่งคอมโพเนนต์ UI บนหน้าจอโดยอัตโนมัติ SDK จะส่งคอมโพเนนต์เหล่านั้นไปยังคลาสที่กำหนดเองที่คุณเขียนโดยตรง ซึ่งก็คือผู้รับมอบสิทธิ์เลย์เอาต์
หากต้องการปรับแต่งเลย์เอาต์ ให้สร้างคลาสที่สอดคล้องกับโปรโตคอลNavigationLayoutDelegate และส่งอินสแตนซ์ไปยังเมธอด setLayoutDelegate(_:) ของ GMSMapView เมื่อใดก็ตามที่สถานะการนำทางเปลี่ยนไป เช่น การเปลี่ยนจากแผนที่ฐานไปเป็นคำแนะนำแบบเลี้ยวต่อเลี้ยวที่ใช้งานอยู่ SDK จะเรียกใช้เมธอด Callback ในผู้รับมอบสิทธิ์และจัดเตรียมคอมโพเนนต์ UI ที่พร้อมใช้งานสำหรับสถานะที่เฉพาะเจาะจงนั้น
เฟรมเวิร์กนี้จะแยกความรับผิดชอบระหว่างแอปกับ SDK อย่างชัดเจน เพื่อมอบประสบการณ์การใช้งานที่ราบรื่นและผสานรวมให้แก่ผู้ใช้ ใช้ตัวแทนเลย์เอาต์เพื่อปรับแต่งสิ่งต่อไปนี้
- สร้างลำดับชั้นการแสดงผล: เลือกคอมโพเนนต์ของ Google และมุมมองธุรกิจที่กำหนดเองที่ต้องการเพิ่มลงในหน้าจอสำหรับสถานะ UI แต่ละสถานะ
- จัดตำแหน่งทุกองค์ประกอบ: กำหนดจุดยึดหน้าจอ ระยะขอบ และตำแหน่งเลย์เอาต์ที่แม่นยำ หลีกเลี่ยงการใช้ข้อจำกัดความกว้างหรือความสูงที่กำหนดเองกับคอมโพเนนต์ของ Google เนื่องจากคอมโพเนนต์ของ Google จะคำนวณขนาดภายในของตนเอง
- จัดเฟรมแผนที่ฐาน: กำหนดขอบเขตที่มองเห็นได้ของกล้องแผนที่โดยใช้พิกัดของคอมโพเนนต์วิวพอร์ต
- จัดเลเยอร์หน้าจอ: เลือกว่าจะให้มุมมองที่กำหนดเองลอยอยู่ด้านบน ด้านล่าง หรืออยู่ข้างๆ ตัวควบคุมในตัวของ Google
ในระหว่างนี้ คุณจะปรับแต่งคอมโพเนนต์ Google ต่อไปนี้ด้วยตัวแทนเลย์เอาต์ไม่ได้
- ขนาดคอมโพเนนต์: ขนาดและขนาดภายในของคอมโพเนนต์ที่ Google มีให้ ซึ่ง SDK จะคำนวณโดยอัตโนมัติ
- เงื่อนไขทริกเกอร์: เมื่อการแจ้งเตือนหรือข้อความแจ้งแบบไดนามิกปรากฏขึ้นตามข้อมูลเส้นทางแบบเรียลไทม์
หลักการติดตั้งใช้งาน
เมื่อเขียน Layout Delegate โปรดคำนึงถึงกฎต่อไปนี้เพื่อป้องกันข้อบกพร่องของเลย์เอาต์หรือข้อขัดข้องของรันไทม์
- ค่าเริ่มต้นคือหน้าจอว่างเปล่า: คอมโพเนนต์ที่ Google มีให้จะปรากฏก็ต่อเมื่อผู้รับมอบสิทธิ์เพิ่มคอมโพเนนต์เหล่านั้นลงในลำดับชั้นการแสดงผลและจัดตำแหน่งอย่างชัดเจน
- ไม่รองรับ API เดิม: ระบบจะไม่รองรับพร็อพเพอร์ตี้และเมธอดที่ควบคุมโมเดลเลย์เอาต์ตามช่องแบบเดิม และอาจไม่ทำงานตามที่คาดไว้เมื่อผู้รับมอบสิทธิ์ที่กำหนดเองทำงานอยู่
- อย่าแก้ไขโครงสร้างมุมมองภายใน: อย่าข้ามหรือแก้ไขลำดับชั้นการแสดงผลของคอมโพเนนต์ที่ Google มีให้ (เช่น การ์ดเลี้ยวหรือการ์ด ETA) เนื่องจากลำดับชั้นการแสดงผลภายในเหล่านี้เป็นรายละเอียดการใช้งานเบื้องหลัง จึงอาจมีการเปลี่ยนแปลงใน SDK รุ่นต่างๆ การแก้ไขอาจทำให้เลย์เอาต์เสียหายระหว่างการอัปเดต SDK ในอนาคต
ความเข้ากันได้กับ API เดิมของ
เพื่อลักษณะการทำงานของเลย์เอาต์ที่เชื่อถือได้เมื่อใช้ตัวแทนเลย์เอาต์ที่กำหนดเอง ให้หลีกเลี่ยงการใช้ API แบบสล็อตเดิมต่อไปนี้ ซึ่งมีแผนที่จะเลิกใช้งาน หากต้องการย้ายข้อมูลแอปที่มีอยู่ ให้แทนที่การใช้ API เหล่านี้ด้วยโค้ดในตัวแทนเลย์เอาต์ที่กำหนดเอง ดังนี้
แสดง API แบบอิงตามช่องโฆษณาเดิม
| API เดิม | การเปลี่ยน Layout Delegate |
|---|---|
isNavigationHeaderEnabled |
อ่านมุมมอง turnCard ใน startGuidance(navMap:newState:oldState:) |
isNavigationFooterEnabled |
อ่านมุมมอง etaCard ใน startGuidance(navMap:newState:oldState:) |
isNavigationTripProgressBarEnabled |
อ่านมุมมอง tripProgressBar ใน startGuidance(navMap:newState:oldState:) |
isNavigationReportIncidentButtonEnabled |
ค้นหาปุ่ม .reporting ในอาร์เรย์ buttons |
setHeaderAccessoryView(_:) และ
invalidateLayout(forAccessoryView:) |
เพิ่มมุมมองของคุณเองลงในเลย์เอาต์และยึดไว้กับ turnCard |
padding และ paddingAdjustmentBehavior |
จัดตำแหน่งมุมมอง viewport เพื่อจัดเฟรมกล้อง |
navigationHeaderLayoutGuide |
ปักหมุดมุมมองที่กำหนดเองไว้ที่ turnCard โดยตรง |
navigationFooterLayoutGuide |
ปักหมุดมุมมองที่กำหนดเองไว้ที่ etaCard โดยตรง |
bottomTrailingButtonsLayoutGuide |
ยึดปุ่มไว้กับ etaCard หรือเส้นบอกแนวคอนเทนเนอร์โดยตรง |
bottomTrailingButtonsAlternateLayoutGuide |
ยึดปุ่มไว้กับปุ่ม .reporting หรือข้อจำกัด wideMode โดยตรง |
navigatorWillPresentPrompt(_:) และ
navigatorDidDismissPrompt(_:) |
ใช้การโทรกลับที่รวดเร็วของ NavigationLayoutDelegate เช่น showPrompt(navMap:newPrompt:) |
ขณะที่มีการตั้งค่าผู้รับมอบสิทธิ์เลย์เอาต์ที่กำหนดเอง ไกด์เลย์เอาต์เดิม (เช่น navigationHeaderLayoutGuide หรือ bottomTrailingButtonsLayoutGuide) และเมธอดมุมมองอุปกรณ์เสริมส่วนหัวจะไม่มีผล ให้ยึดมุมมองของคุณตามมุมมองที่ Google มีให้แทน
พร็อพเพอร์ตี้ compassButton ของ GMSUISettings ยังคงควบคุมเข็มทิศขณะที่ UI การนำทางปิดใช้อยู่ ขณะที่เปิดใช้การนำทาง ผู้รับมอบสิทธิ์จะตัดสินใจว่าจะแสดงเข็มทิศจากอาร์เรย์ buttons หรือไม่
รายการตรวจสอบ Essentials
ทำตามขั้นตอนและข้อกำหนดที่สำคัญต่อไปนี้เพื่อติดตั้งใช้งาน Layout Delegate ให้สำเร็จ
-
ตั้งค่าผู้รับมอบสิทธิ์ก่อนเปิดใช้การนำทาง: เรียกใช้
setLayoutDelegate(_:)ในGMSMapViewก่อนเปิดใช้การนำทางด้วยisNavigationEnabledหรือenableNavigation(with:)เมธอดจะแสดงข้อผิดพลาดหากเปิดใช้การนําทางอยู่แล้ว หรือหากคุณสร้างมุมมองแผนที่สําหรับหน้าจอ CarPlay มุมมองแผนที่จะเก็บการอ้างอิงที่ชัดเจนไปยังผู้รับมอบสิทธิ์ของคุณ หากต้องการคืนค่าเลย์เอาต์ในตัว ให้ส่งnilขณะที่การนำทางปิดใช้อยู่// Set the layout delegate before you enable navigation. do { try mapView.setLayoutDelegate(MyLayoutDelegate()) } catch { print("Couldn't set the layout delegate: \(error)") } mapView.isNavigationEnabled = true
-
แนบมุมมองที่จำเป็น: คุณต้องเพิ่มโลโก้ Google (
googleLogo) และวิวพอร์ต (viewport) ลงในคอนเทนเนอร์เลย์เอาต์ในสถานะการนำทางทั้งหมด -
ใช้ขนาดคงที่: SDK จะกำหนดขนาดสำหรับคอมโพเนนต์หลายรายการที่ Google มีให้ อย่าใช้ข้อจำกัดความกว้างหรือความสูงที่กำหนดเองกับองค์ประกอบต่อไปนี้
- การ์ดบอกทาง
- การ์ด ETA
- พรอมต์ที่เข้ามาใหม่
- โลโก้ Google
- วิดเจ็ตความเร็ว
-
เพิ่มองค์ประกอบเป็นมุมมองย่อย: เพิ่มมุมมองที่ Google มีให้เป็นมุมมองย่อยโดยตรงลงในมุมมองคอนเทนเนอร์ที่กำหนดเองหรือลงใน
GMSMapViewเอง -
จัดแนวโดยใช้เส้นบอกแนวเลย์เอาต์: มุมมองที่ Google มีให้จะมีระยะขอบภาพในตัว หากต้องการจัดองค์ประกอบที่กำหนดเองให้สอดคล้องกับคอมโพเนนต์ที่ Google มีให้อย่างสมบูรณ์แบบ ให้จำกัดมุมมองของคุณไว้ที่พร็อพเพอร์ตี้
layoutMarginsGuideของมุมมอง Google -
ล้างข้อมูลเมื่อออกจากสถานะ: ใน
stopGuidance(navMap:newState:oldState:)ให้นำมุมมองคำแนะนำที่ใช้งานอยู่ออกและปิดใช้งานข้อจำกัดก่อนที่จะคืนค่าเลย์เอาต์ที่พร้อมสำหรับการนำทาง ในdisableNavigationUI(navMap:oldState:)ให้นำมุมมองและเส้นบอกแนวเลย์เอาต์ทั้งหมดที่คุณเพิ่มออก รวมถึงข้อความแจ้งบนหน้าจอด้วย ซึ่งจะป้องกันไม่ให้มุมมองจากสถานะหนึ่งทับซ้อนกับสถานะถัดไป -
การช่วยเหลือพิเศษและการรองรับการอ่านจากขวาไปซ้าย: มุมมองที่ Google มีให้เป็นไปตามมาตรฐานการช่วยเหลือพิเศษ แต่ผู้รับมอบสิทธิ์มีหน้าที่รับผิดชอบต่อการช่วยเหลือพิเศษของเลย์เอาต์โดยรวม หากต้องการรองรับภาษาที่อ่านจากขวาไปซ้าย ให้ใช้ Anchor นำหน้าและต่อท้ายแทน Anchor ซ้ายและขวา
-
หลีกเลี่ยง API เลย์เอาต์เดิม: อย่าใช้พร็อพเพอร์ตี้การแก้ไขเลย์เอาต์เดิมใน
GMSUISettingsหรือไกด์เลย์เอาต์เดิมในGMSMapViewขณะที่ตัวแทนที่กำหนดเองทำงานอยู่ SDK อาจละเว้นการโทรเวอร์ชันเดิมเหล่านี้เมื่อมีการแนบผู้รับมอบสิทธิ์ที่กำหนดเอง
สถานะ UI และคอมโพเนนต์ของ Google
เมื่อเกิดการเปลี่ยนสถานะการนำทาง SDK จะส่งออบเจ็กต์ NavigationUIState แบบอ่านอย่างเดียวไปยัง Callback ของผู้รับมอบสิทธิ์ ออบเจ็กต์นี้จะรวมแฟล็กการกำหนดค่าเลย์เอาต์ปัจจุบันเข้ากับคอมโพเนนต์ของ Google (เช่น อินสแตนซ์ UIView) ที่คุณต้องใช้ในการแสดงผลหน้าจอ
ผู้รับมอบสิทธิ์เลย์เอาต์จะจัดการลำดับชั้นการแสดงผลในสถานะการทำงาน 4 สถานะ แผนภาพสถานะต่อไปนี้แสดงวิธีที่ SDK เปลี่ยนสถานะการนำทางและเมธอด Callback ที่ SDK ดำเนินการกับผู้รับมอบสิทธิ์
คอมโพเนนต์ Google ที่เฉพาะเจาะจงซึ่งมอบให้กับผู้รับมอบสิทธิ์จะขึ้นอยู่กับระยะปัจจุบันของวงจรการนําทาง
องค์ประกอบที่จำเป็น (สถานะการนำทางทั้งหมด)
ไม่ว่าจะอยู่ในระยะการนำทางใด คุณต้องรวม จัดตำแหน่ง และแสดงคอมโพเนนต์ของ Google ต่อไปนี้ให้เห็นในลำดับชั้นการแสดงผล
โลโก้ Google (
googleLogo): คอมโพเนนต์นี้จะแสดงโลโก้ Google Maps ที่จำเป็น หากเปิดใช้ปุ่มจัดให้อยู่ตรงกลาง โลโก้จะเปลี่ยนเป็นปุ่มนี้โดยอัตโนมัติเมื่อผู้ขับเลื่อนแผนที่ออกจากยานพาหนะของตน ด้วยเหตุนี้ ตำแหน่งของโลโก้จึงเป็นตัวกำหนดตำแหน่งที่ปุ่มจัดให้อยู่ตรงกลางอีกครั้งจะปรากฏขึ้น เราขอแนะนำให้วางโลโก้ไว้ที่มุมล่างซ้ายของเลย์เอาต์เพื่อให้ตรงกับความคาดหวังของ UI แผนที่มาตรฐานวิวพอร์ต (
viewport):UIViewที่มองไม่เห็นซึ่งกำหนดขอบเขตการจัดเฟรมของกล้อง ตำแหน่งของวิวพอร์ตจะเป็นตัวกำหนดตำแหน่งที่ SDK จะจัดกึ่งกลางเครื่องหมายบั้งของยานพาหนะและวาดเส้นทางที่ใช้งานอยู่ จัดตำแหน่งวิวพอร์ตให้ครอบคลุมพื้นที่ที่เปิดอยู่และไม่มีสิ่งบดบังบนหน้าจอ โดยหลีกเลี่ยงการวางซ้อนทับองค์ประกอบที่ทึบแสง เช่น Bottom Sheet ที่กำหนดเอง เพื่อความปลอดภัย
คอมโพเนนต์ที่พร้อมสำหรับการนำทาง รายการ
ในระหว่างสถานะพร้อมนำทาง UI จะยังคงมีขนาดเล็กที่สุดเพื่อมุ่งเน้นไปที่แผนที่ฐาน ออบเจ็กต์ NavigationReadyUIState ให้สิทธิ์เข้าถึงสิ่งต่อไปนี้
- buttons: อาร์เรย์ของมุมมองการดำเนินการแบบลอยที่กำหนดค่าไว้สำหรับแผนที่ฐาน (โดยทั่วไปจะเป็นเพียงปุ่มเข็มทิศ) เนื่องจาก Google อาจเพิ่มหรือจัดเรียงปุ่มใหม่ใน SDK รุ่นต่อๆ ไป โปรดหลีกเลี่ยงการใช้ดัชนีอาร์เรย์แบบคงที่ แต่ให้ทำซ้ำผ่านอาร์เรย์ อ่านประเภทของปุ่มแต่ละปุ่มโดยเปรียบเทียบพร็อพเพอร์ตี้
typeกับNavigationButtonKnownType.compassและดึงข้อมูลมุมมองจริงโดยเรียกใช้พร็อพเพอร์ตี้viewโปรดทราบว่าปุ่มที่แน่นอนในอาร์เรย์นี้แตกต่างจากปุ่มที่ใช้ได้ในระหว่างการนำทางที่ใช้งานอยู่
คอมโพเนนต์คำแนะนำที่ใช้งานอยู่ รายการ
เมื่อคำแนะนำแบบเลี้ยวต่อเลี้ยวเริ่มต้นขึ้น SDK จะปลดล็อกชุดการควบคุมการนำทางทั้งหมด ออบเจ็กต์ ActiveGuidanceUIState ให้สิทธิ์เข้าถึงคอมโพเนนต์ Google ต่อไปนี้
turnCard: แบนเนอร์ส่วนหัวหลักที่แสดงทิศทางการเคลื่อนที่ ระยะทาง และคำแนะนำช่องทางที่กำลังจะถึง วางไว้ที่ด้านบนของเลย์เอาต์เพื่อสร้างลำดับชั้นการนำทางที่คุ้นเคยและยึดมุมมองที่กำหนดเองไว้รอบๆetaCard: แบนเนอร์ส่วนท้ายที่แสดงเวลาถึงโดยประมาณ ระยะเวลาการเดินทางที่เหลือ และระยะทางที่เหลือไปยังจุดหมาย วางไว้ที่ขอบด้านล่างของหน้าจอ หรือผสานรวมพิกัดของวิดเจ็ตกับชีตการจัดการงานที่กำหนดเองtripProgressBar: แถบความคืบหน้าแนวตั้งที่แสดงให้คนขับเห็นว่าเดินทางมาไกลแค่ไหนแล้วตามเส้นทางปัจจุบัน ซึ่งแตกต่างจากเลย์เอาต์เดิมที่กำหนดให้องค์ประกอบนี้อยู่ติดกับขอบแผนที่อย่างเคร่งครัด คุณมีอิสระอย่างเต็มที่ในการยึดองค์ประกอบนี้ไว้ที่ใดก็ได้ เช่น ตามขอบด้านข้างของคอนเทนเนอร์ที่กำหนดเองspeedWidget: การควบคุมแบบลอยสำหรับมาตรวัดความเร็วและขีดจำกัดความเร็วที่ระบุไว้ มุมมองนี้จะปรับขนาดแบบไดนามิกระหว่างสถานะภาพ 4 สถานะที่รันไทม์ (ไม่แสดงอะไรเลย แสดงเฉพาะความเร็วปัจจุบัน แสดงเฉพาะขีดจำกัดความเร็ว หรือแสดงทั้ง 2 อย่าง) โดยขึ้นอยู่กับการตั้งค่า API (พร็อพเพอร์ตี้shouldDisplaySpeedometerและshouldDisplaySpeedLimitของGMSMapView) และความพร้อมใช้งานของข้อมูล เนื่องจากวิดเจ็ตสามารถเปลี่ยนขนาดเหล่านี้ได้โดยไม่ต้องแจ้งให้ทราบล่วงหน้า ให้ยึดมุมมองโดยรอบโดยใช้ข้อจำกัดเสมอเพื่อให้เลย์เอาต์ปรับโดยอัตโนมัติเพื่อป้องกันการทับซ้อนกันของพื้นที่
buttons: อาร์เรย์ที่ขยายของมุมมองการดำเนินการแบบลอยสำหรับสถานะคำแนะนำที่ใช้งานอยู่ (โดยปกติจะรวมทั้งเข็มทิศและปุ่มรายงานเหตุการณ์) เช่นเดียวกับในสถานะพร้อมสำหรับการนำทาง คุณสามารถค้นหาและดึงข้อมูลมุมมองปุ่มแต่ละรายการได้โดยกรองอาร์เรย์ตามtype(.compassหรือ.reporting) และดึงข้อมูลมุมมองจริงโดยใช้พร็อพเพอร์ตี้viewจากนั้นคุณจะจัดตำแหน่งกล้องแต่ละตัวแยกกัน หรือเพิ่มกล้องลงในGMSNavigationHidingStackViewก็ได้ มุมมองกองซ้อนนี้จะจัดเรียงปุ่มในแนวตั้ง และเมื่อพื้นที่หมดลง จะซ่อนปุ่มโดยเริ่มจากส่วนท้ายของอาร์เรย์arrangedSubviewsอย่าตั้งค่าisHiddenในมุมมองที่จัดการ
NavigationStyleValues.headerNominalHeight ซึ่งสูงพอสำหรับเนื้อหาการ์ดเลี้ยวทั้งหมด หากต้องการให้การจัดเฟรมกล้องคงที่ ให้จำกัดด้านบนของ viewport ไว้ที่ขอบด้านล่างของ turnCard โดยตรง ดังที่แสดงในตัวอย่างการติดตั้งใช้งาน
องค์ประกอบพรอมต์แบบไดนามิก รายการ
ข้อความแจ้ง เช่น การแจ้งเตือนเหตุการณ์หรือคำเตือนเกี่ยวกับกล้องจับความปลอดภัย จะทริกเกอร์อย่างอิสระในระหว่างการนำทางที่ใช้งานอยู่
เมื่อข้อความแจ้งพร้อมแสดง SDK จะเรียกใช้ Callback showPrompt(navMap:newPrompt:) ของผู้รับมอบสิทธิ์และส่งมุมมอง newPrompt โดยตรง ผู้รับมอบสิทธิ์มีหน้าที่จัดตำแหน่งข้อความแจ้งนี้ให้อยู่เหนือเลย์เอาต์อย่างราบรื่น (โดยปกติจะยึดไว้ที่ขอบด้านล่างของคอนเทนเนอร์แผนที่)
เนื่องจากข้อความแจ้งที่เข้ามาจะครอบคลุมส่วนล่างของหน้าจอ คุณจึงต้องอัปเดตเลย์เอาต์เพื่อป้องกันไม่ให้ข้อความแจ้งซ้อนทับวิวพอร์ต โลโก้ Google หรือปุ่มที่จัดชิดด้านล่าง
นอกจากนี้ ผู้รับมอบสิทธิ์ยังควบคุมวิธีที่พรอมต์เข้าและออกจากหน้าจอด้วย โปรดดูรายละเอียดที่หัวข้อการปรับแต่งพรอมต์
การจัดการขนาดหน้าจอและโหมดหน้าจอกว้าง
ในการจัดการการเปลี่ยนแปลงขนาดแผนที่และการวางแนวหน้าจอ ตัวแทนเลย์เอาต์จะใช้ฟีเจอร์ต่อไปนี้
การปรับขนาดมุมมอง: ปรับเลย์เอาต์เมื่อใดก็ตามที่ขนาดจริงของแผนที่มีการเปลี่ยนแปลง
โหมดกว้าง: สลับไปใช้ตัวแปรเลย์เอาต์รูปแบบกว้างเมื่อแผนที่กว้างพอ
การตอบสนองต่อการปรับขนาดมุมมอง
เลย์เอาต์แบบแยกหน้าจอ แถบเลื่อนคอนเทนเนอร์ และการหมุนอุปกรณ์มักจะทริกเกอร์ CallbacksizeChanged(navMap:newState:) ใช้ Callback นี้เพื่อทำการปรับเปลี่ยน UI ที่กำหนดเองให้ปรับเปลี่ยนตามอุปกรณ์โดยทั่วไป ใช้ sizeChanged(navMap:newState:) เพื่อเปลี่ยนตำแหน่งองค์ประกอบที่กำหนดเองสำหรับสัดส่วนภาพใหม่ของหน้าจอ ใช้เบรกพอยต์ความกว้างหรือความสูงที่กำหนดเอง และตรวจจับเมื่อเหตุการณ์การปรับขนาดสลับโหมดกว้างเพื่อสลับตัวแปรเลย์เอาต์อย่างปลอดภัย
ทำความเข้าใจโหมดกว้าง
โหมดกว้างจะเปิดใช้งานเมื่อคอนเทนเนอร์แผนที่กว้างพอที่จะแสดงคอมโพเนนต์ UI เคียงข้างกัน
อ่านบูลีน wideMode จากออบเจ็กต์สถานะเพื่อเปลี่ยนตำแหน่งองค์ประกอบ UI ที่กำหนดเองและทำให้ตรงกลางแผนที่ชัดเจนสำหรับคนขับ เนื่องจากคอมโพเนนต์ที่ Google มีให้ เช่น การ์ดเลี้ยวและการ์ด ETA จะหดและปรับรูปร่างใหม่โดยอัตโนมัติในโหมดกว้าง การอ่านบูลีนนี้จะช่วยให้มั่นใจได้ว่าเลย์เอาต์ของคุณจะปรับเปลี่ยนในขณะเดียวกับที่คอมโพเนนต์ของ Google อัปเดต
พิจารณาตัวอย่างต่อไปนี้สำหรับการจัดตำแหน่งคอมโพเนนต์เลย์เอาต์ในโหมดมาตรฐานและโหมดกว้าง
โหมดแนวตั้งมาตรฐาน: วางการ์ดเลี้ยวไว้ที่ด้านบนของหน้าจอและการ์ด ETA ไว้ที่ด้านล่าง
โหมดกว้าง: แสดงการ์ดเลี้ยวและการ์ด ETA ไว้ที่ขอบด้านหน้าของหน้าจอ โดยให้การ์ดเลี้ยวอยู่ด้านบนและการ์ด ETA อยู่ด้านล่าง การวางการ์ดใดการ์ดหนึ่งให้ห่างจากขอบด้านหน้าจะทำให้เกิดลักษณะการทำงานที่ไม่ได้กำหนดไว้ คุณสามารถวางวิวพอร์ตและเนื้อหาของคุณเองไว้ที่ใดก็ได้ โดยตัวเลือกที่นิยมคือด้านท้ายของการ์ด
กำลังตรวจสอบโหมดกว้างระหว่างการเปลี่ยนสถานะ
หากเลย์เอาต์รองรับรูปแบบโหมดกว้าง ให้ประเมินบูลีน wideMode ภายใน Callback การเปลี่ยนสถานะทุกครั้ง แทนที่จะอาศัยเพียง sizeChanged(navMap:newState:)
enableNavigationUI(navMap:newState:) และ startGuidance(navMap:newState:oldState:) เพื่อจัดการการเปิดแอปครั้งแรกในแนวนอน วิธีนี้ช่วยให้มั่นใจว่าเลย์เอาต์จะประเมินสถานะโหมดกว้างและเปิดใช้งานตัวแปรเลย์เอาต์มาตรฐานหรือแบบกว้างที่ถูกต้องทันทีที่สถานะการนําทางใหม่เริ่มต้นขึ้น
การปรับให้สอดคล้องกับรูปแบบของ Google
SDK มีคลาส NavigationStyleValues เพื่อช่วยให้มุมมองที่กำหนดเองตรงกับระยะห่างของคอมโพเนนต์ที่ Google มีให้ พร็อพเพอร์ตี้คลาสจะแสดงผลการวัดเป็นจุด
ตัวอย่างเช่น หากต้องการวางปุ่มที่กำหนดเองไว้ที่มุมด้านบนของหน้าจอตรงข้ามกับการ์ดเลี้ยว ให้ใช้ NavigationStyleValues.headerTopPadding เป็นระยะขอบด้านบนของปุ่ม จากนั้นปุ่มจะจัดแนวให้ตรงกับขอบด้านบนของการ์ดเลี้ยว
การวัดที่ใช้ได้มีดังนี้
NavigationStyleValues.buttonHorizontalPaddingNavigationStyleValues.buttonVerticalPaddingNavigationStyleValues.defaultCornerRadiusNavigationStyleValues.elementHorizontalPaddingNavigationStyleValues.headerFooterHorizontalPaddingNavigationStyleValues.headerNominalHeightNavigationStyleValues.headerTopPadding
มุมมองที่ Google มีให้ยังรวมถึงระยะขอบในตัวด้วย เมื่อวางซ้อนมุมมองที่ Google มีให้ 2 มุมมอง ให้จำกัดมุมมองหนึ่งไว้ที่ layoutMarginsGuide ของอีกมุมมองหนึ่งเพื่อไม่ให้ระยะขอบเพิ่มขึ้น เช่น หากต้องการวางวิดเจ็ตความเร็วไว้เหนือการ์ด ETA ให้จำกัด Anchor ด้านล่างของ speedWidget ไว้ที่ etaCard.layoutMarginsGuide.topAnchor
การปรับแต่งพรอมต์
ตัวแทนเลย์เอาต์จะควบคุมวิธีที่พรอมต์เข้าและออกจากหน้าจอ รวมถึงมุมที่โค้งมน
พรอมต์การสร้างภาพเคลื่อนไหว
SDK จะไม่แสดงภาพเคลื่อนไหวของข้อความแจ้งให้คุณ ผู้รับมอบสิทธิ์จะเป็นผู้กำหนดลักษณะการปรากฏและหายไปของข้อความแจ้งแต่ละรายการ ดังนี้
- ใน
showPrompt(navMap:newPrompt:)ให้เพิ่มพรอมต์ลงในมุมมองแผนที่ที่ตำแหน่งเริ่มต้น แล้วทำให้เคลื่อนไหวเข้าสู่ตำแหน่ง เช่น เริ่มแสดงข้อความแจ้งใต้พื้นที่ที่มองเห็นได้ หรือหลัง Bottom Sheet โดยให้ขอบด้านบนอยู่ในแนวเดียวกับด้านบนของชีต - ใน
hidePrompt(navMap:oldPrompt:)ให้ทำให้ข้อความแจ้งเคลื่อนไหวออกจากมุมมอง แล้วนำออกจากมุมมองหลักเมื่อภาพเคลื่อนไหวเสร็จสิ้น - ใน
changePrompt(navMap:newPrompt:oldPrompt:)ให้เรียกใช้ภาพเคลื่อนไหวที่ซ่อนและแสดงพร้อมกันเพื่อแทนที่พรอมต์หนึ่งด้วยอีกพรอมต์หนึ่ง
ตัวอย่างการใช้งานใช้วิธีนี้
หากต้องการปิดข้อความแจ้งเกี่ยวกับการจราจร เส้นทางที่ดีกว่า และเหตุการณ์ ให้ตั้งค่าพร็อพเพอร์ตี้ shouldDisplayPrompts ของ GMSNavigator เป็น false
การปัดเศษมุมพรอมต์
ข้อความแจ้งเป็นไปตามโปรโตคอล NavigationRoundedCornerView คุณจึงเลือกได้ว่าจะให้มุมใดโค้งมน โดยค่าเริ่มต้น พรอมต์จะปัดเฉพาะมุมด้านบนเท่านั้น ซึ่งเหมาะกับพรอมต์ที่ตรึงไว้ที่ขอบด้านล่างของหน้าจอ
หากคุณวางข้อความแจ้งไว้เหนือเนื้อหาอื่นๆ เช่น Bottom Sheet ให้ปัดมุมทั้ง 4 มุมให้โค้งมน ดังนี้
newPrompt.roundedCorners = [
.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]
ตัวอย่าง: การติดตั้งใช้งานเลย์เอาต์ตามข้อจำกัด
ตัวอย่างต่อไปนี้แสดงตัวแทนเลย์เอาต์พื้นฐานที่ใช้ข้อจำกัดของ Auto Layout เพื่อจัดตำแหน่งการ์ดเลี้ยว การ์ด ETA ปุ่ม โลโก้ Google และวิวพอร์ต และยังเลื่อนข้อความแจ้งเข้าและออกจากหน้าจอด้วย เพื่อให้ตัวอย่างสั้นลง จึงไม่ได้จัดการโหมดกว้าง วิดเจ็ตความเร็ว หรือแถบความคืบหน้าของการเดินทาง จึงเหมาะสำหรับแอปที่รองรับการวางแนวตั้งเท่านั้น
import GoogleNavigation
import UIKit
/// A layout delegate that positions the essential navigation UI elements: the
/// turn card, ETA card, buttons, Google logo, and viewport. It also slides
/// prompts on and off the screen.
@MainActor
final class BasicLayoutDelegate: NSObject, NavigationLayoutDelegate {
/// How long prompts take to slide on or off the screen, in seconds.
private static let promptAnimationDuration: TimeInterval = 0.4
/// The views and constraints for one navigation state.
@MainActor
private final class StateElements {
let buttonStack = GMSNavigationHidingStackView(
position: .bottom, alignment: .trailing)
var constraints: [NSLayoutConstraint] = []
private unowned let navMap: GMSMapView
private let buttons: [UIView]
private let topLevelViews: [UIView]
init(
navMap: GMSMapView, buttons: [NavigationUIButton],
topLevelViews: [UIView]
) {
self.navMap = navMap
self.buttons = buttons.map { $0.view }
self.topLevelViews = topLevelViews
}
/// Adds the views to the map view, and then activates the constraints.
func activate() {
topLevelViews.forEach { navMap.addSubview($0) }
buttonStack.arrangedSubviews = buttons
navMap.addSubview(buttonStack)
NSLayoutConstraint.activate(constraints)
}
/// Deactivates the constraints, and then removes the views.
func deactivate() {
NSLayoutConstraint.deactivate(constraints)
buttonStack.removeFromSuperview()
buttonStack.arrangedSubviews = []
topLevelViews.forEach { $0.removeFromSuperview() }
}
}
private var navigationReadyElements: StateElements?
private var activeGuidanceElements: StateElements?
private var currentPrompt: NavigationPromptView?
private var promptBottomConstraint: NSLayoutConstraint?
/// Constraints that apply in both the navigation-ready and active guidance
/// states.
private var sharedConstraints: [NSLayoutConstraint] = []
/// Marks the bottom of the map content area. The guide stays above the ETA
/// card and above any prompt that's on the screen.
private let mapContentsBottomGuide = UILayoutGuide()
// MARK: - Navigation-ready state
func enableNavigationUI(
navMap: GMSMapView, newState: NavigationReadyUIState
) {
let safeArea = navMap.safeAreaLayoutGuide
let bottomGuide = mapContentsBottomGuide
navMap.addLayoutGuide(bottomGuide)
// Keep the guide at the bottom of the safe area unless a higher-priority
// constraint pushes it up.
let lowPriorityBottom = bottomGuide.bottomAnchor.constraint(
equalTo: safeArea.bottomAnchor)
lowPriorityBottom.priority = .defaultLow
sharedConstraints = [
bottomGuide.heightAnchor.constraint(equalToConstant: 0),
lowPriorityBottom,
]
let logo = newState.googleLogo
let viewport = newState.viewport
let elements = StateElements(
navMap: navMap, buttons: newState.buttons,
topLevelViews: [logo, viewport])
let buttonStack = elements.buttonStack
elements.constraints = sharedConstraints + [
buttonStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
buttonStack.topAnchor.constraint(equalTo: safeArea.topAnchor),
buttonStack.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
logo.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
logo.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
viewport.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
viewport.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
viewport.topAnchor.constraint(equalTo: safeArea.topAnchor),
viewport.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
]
elements.activate()
navigationReadyElements = elements
}
func disableNavigationUI(
navMap: GMSMapView, oldState: NavigationReadyUIState
) {
currentPrompt?.removeFromSuperview()
currentPrompt = nil
promptBottomConstraint = nil
navigationReadyElements?.deactivate()
navigationReadyElements = nil
navMap.removeLayoutGuide(mapContentsBottomGuide)
}
// MARK: - Active guidance state
func startGuidance(
navMap: GMSMapView, newState: ActiveGuidanceUIState,
oldState: NavigationReadyUIState
) {
navigationReadyElements?.deactivate()
let safeArea = navMap.safeAreaLayoutGuide
let bottomGuide = mapContentsBottomGuide
let turnCard = newState.turnCard
let etaCard = newState.etaCard
let logo = newState.googleLogo
let viewport = newState.viewport
let elements = StateElements(
navMap: navMap, buttons: newState.buttons,
topLevelViews: [turnCard, etaCard, viewport, logo])
let buttonStack = elements.buttonStack
elements.constraints = sharedConstraints + [
turnCard.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
turnCard.topAnchor.constraint(equalTo: navMap.topAnchor),
etaCard.leadingAnchor.constraint(equalTo: navMap.leadingAnchor),
etaCard.bottomAnchor.constraint(equalTo: navMap.bottomAnchor),
bottomGuide.bottomAnchor.constraint(
lessThanOrEqualTo: etaCard.topAnchor),
buttonStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
buttonStack.topAnchor.constraint(equalTo: turnCard.bottomAnchor),
buttonStack.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
logo.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
logo.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
// Frame the camera in the open area below the turn card.
viewport.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
viewport.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
viewport.topAnchor.constraint(equalTo: turnCard.bottomAnchor),
viewport.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
]
elements.activate()
activeGuidanceElements = elements
}
func stopGuidance(
navMap: GMSMapView, newState: NavigationReadyUIState,
oldState: ActiveGuidanceUIState
) {
activeGuidanceElements?.deactivate()
activeGuidanceElements = nil
navigationReadyElements?.activate()
}
// MARK: - Prompts
func showPrompt(navMap: GMSMapView, newPrompt: NavigationPromptView) {
navMap.addSubview(newPrompt)
currentPrompt = newPrompt
// Start with the prompt just below the bottom edge of the map view.
let bottomConstraint = newPrompt.bottomAnchor.constraint(
equalTo: navMap.bottomAnchor, constant: newPrompt.bounds.height)
promptBottomConstraint = bottomConstraint
NSLayoutConstraint.activate([
bottomConstraint,
newPrompt.leadingAnchor.constraint(
equalTo: navMap.safeAreaLayoutGuide.leadingAnchor),
// Keep the map content above the prompt.
mapContentsBottomGuide.bottomAnchor.constraint(
lessThanOrEqualTo: newPrompt.topAnchor),
])
// Lay out the prompt in its starting position, and then slide it up.
navMap.layoutIfNeeded()
UIView.animate(withDuration: Self.promptAnimationDuration) {
bottomConstraint.constant = 0
navMap.layoutIfNeeded()
}
}
func hidePrompt(navMap: GMSMapView, oldPrompt: NavigationPromptView) {
guard let bottomConstraint = promptBottomConstraint else { return }
promptBottomConstraint = nil
currentPrompt = nil
// Slide the prompt down, and then remove it.
UIView.animate(withDuration: Self.promptAnimationDuration) {
bottomConstraint.constant = oldPrompt.bounds.height
navMap.layoutIfNeeded()
} completion: { _ in
oldPrompt.removeFromSuperview()
}
}
func changePrompt(
navMap: GMSMapView, newPrompt: NavigationPromptView,
oldPrompt: NavigationPromptView
) {
// Slide the old prompt out and the new prompt in at the same time.
hidePrompt(navMap: navMap, oldPrompt: oldPrompt)
showPrompt(navMap: navMap, newPrompt: newPrompt)
}
}
หากต้องการใช้ผู้รับมอบสิทธิ์ ให้ตั้งค่าในมุมมองแผนที่ก่อนเปิดใช้การนำทางตามที่อธิบายไว้ในรายการตรวจสอบที่จำเป็น
ข้อจำกัดและปัญหาที่ทราบ
การปรับแต่งเลย์เอาต์ไม่มีผลกับ CarPlay ซึ่งใช้เทมเพลตเลย์เอาต์แบบคงที่ หากคุณเรียกใช้ setLayoutDelegate(_:) ในมุมมองแผนที่ที่สร้างขึ้นสำหรับหน้าจอ CarPlay เมธอดจะแสดงข้อผิดพลาด
สำหรับปัญหาที่ส่งผลต่อ SDK บางเวอร์ชัน โปรดดูปัญหาที่ทราบในบันทึกประจำรุ่น