December 13, 2014

ทบทวนพื้นฐาน Auto Layout

ก่อนหน้านี้ที่ Auto Layout ถูกแนะนำใหม่ๆ เพื่อน iOS developer หลายคนพยายามหลีกเลี่ยงมัน (รวมถึงตัวผมเองด้วย) อาจเพราะมันเข้าใจได้ยากหรือเปล่าไม่รู้ แต่ที่รู้ๆ ก็คือต้องเริ่มต้นศึกษาทำความเข้าใจมันใหม่ แทนที่ระบบ layout เดิมของ iOS SDK รุ่นก่อนๆ

Intro

บันทึกนี้จะแสดงวิธีการใช้งาน Auto Layout แบบ Programmically ซึ่งก็แปลว่าเราจะไม่ใช้ xib หรือ storyborad กับ auto layout เลย

เราจะใช้ Visual Format Language (VFL) ในการกำหนด constraint หรือข้อกำหนดเกี่ยวกับพฤติกรรม รวมถึงการระบุตำแหน่ง และขนาดของ view โดย syntax ที่ใช้นั้นไม่ซับซ้อนเลย เช่น หากเราต้องการกำหนดความกว้างของ element ให้มีขนาด 100 เราก็สามารถเขียนได้ดังนี้



หรือถ้าเราต้องการกำหนดความสูง ก็ทำแบบนี้



ตัวอักษรตัวแรกเอาไว้บอกว่าเราจะกำหนดขนาดให้กับ deimesion ไหน ตัว H ก็คือ horizontal ส่วนตัว V ก็คือ Vertical

และเวลาเรากำหนด constraint เหล่านี้ผ่าน VFL เราสามารถกำหนดผ่าน method addConstraint: และ removeConstraint: ของ UIView ได้



เริ่มเขียนโปรแกรม

setup ตัวแปรก่อนดังนี้
  1. สร้างตัวแปรตั้งต้นขึ้นมา เป็น instance ของ UIView ชื่อ self.redView และ self.yellowView
  2. กำหนด translatesAutoresizingMaskIntoConstraints = NO ให้กับทั้ง  2 instance โดยสาเหตุที่ต้องกำหนดค่านี้ให้เป็น NO ก็เพราะว่า เมื่อใดก็ตามที่เราต้องจัดการ Auto Layout ต้วยตัวเอง เราต้องกำหนดค่านี้เพื่อรับประกันว่าจะไม่มี constraint อื่นถูกสร้างขึ้นมาเองโดยอัตโนมัติ แล้วมา conflict กับ constraint ของเรา (กรณีที่เราเพิ่ม constraint ใน Interface Builder property นี้จะถูกกำหนดเป็น NO ให้อัตโนมัติอยู่แล้ว)
  3. add ทั้ง 2 view ลงไปใน superview เดียวกัน (กรณีที่จะกล่าวถึงต่อไปจะ add ลงไปใน self.view) โดยไม่ต้องกำหนด frame ให้กับ view เลย (ในโลกของ auto layout นี่ให้ลืมการกำหนด layout ผ่าน frame ไปก่อนได้เลย)


ตัวอย่างที่ 1 ทดลองเขียน constraint ด้วย VFL

ลองกำหนดตำแหน่ง และขนาดให้ redView บนหน้าจอด้วย VFL แต่ก่อนที่จะใช้ VFL นั้น เราต้องกำหนดชื่อของ view ก่อน เพื่อเอาไว้อ้างถึงใน VFL ไม่อย่างนั้นมันจะไม่รู้ว่าเราอ้างถึง view ไหน ดังนี้


ต่อไปนี้ เวลาเราจะอ้างถึง self.redView ใน VFL เราจะเรียกมันสั้นๆ ว่า redView ก็พอ

โจทย์คือ เราต้องการแปะ redView ไว้บนหน้าจอ โดยมีขนาด 100x100 และห่างด้านซ้าย 20 ห่างด้านบน 30



จากนั้นเราจะสร้างสร้าง constraint ด้วย method constraintsWithVisualFormat:options:metrics:views: ของคลาส NSLayoutConstraint โดยที่ method นี้จะ return array ของ NSLayoutConstrint ออกมาโดยขึ้นอยู่กับ parameter ต่างๆ ที่เรากำหนดให้

นี่คือการกำหนดความกว้าง และความสูงให้ redView ตามโจทย์



จากนั้นก็กำหนดตำแหน่งให้กับ redView อยู่ห่างด้านซ้าย 20 และด้านบน 30 แบบนี้


จะสังเกตเห็นว่า VFL หน้าตาเปลี่ยนไปไม่เหมือนตอนแรก แต่ก็สามารถทำความเข้าใจได้โดยง่าย และตรงไปตรงมา เช่น @"H:|-20-[redView]" ก็อ่านตรงๆ ได้เลยว่า ให้ redView อยู่ห่างจากด้านซ้ายอิงจาก Superview (ห่างทาง horizontal) ไป 30 โดยสัญลักษณ์ | (pipe) ก็เอาไว้อ้างถึง superview นั่นเอง ถ้าเป็น @"V:|-30-[redView]" ก็จะเป็นการกำหนดระยะห่างจากด้านบนแทน

ในทางตรงกันข้าม ถ้าเขียน -20-| ไว้ทางด้านขวา ก็จะหมายถึงรักษาระยะห่างทางด้านขวาไว้ 30 แทน

จุดที่แตกต่างอีกจุดก็คือการเพิ่ม constraint ให้ แทนที่จะกำหนดให้กับ redView เหมือนกับการกำหนดขนาด แต่ครั้งนี้เป็นการเพิ่ม constraint ให้กับ self.view แทนไปซะได้

ส่วนตอนไหนจะกำหนดให้ redView ตอนไหนจะกำหนดให้ superview นั้นวิธีจำง่ายๆ ก็คือ การระบุพิกัด เป็นหน้าที่ของ superview ที่ต้อง assign ให้กับ child view ดังนั้นเราจึงต้องกำหนด constraint ให้กับ superview

มาถึงตรงนี้เราก็สามารถ compile & run ดูผลลัพธ์ได้แล้ว

ตัวอย่างที่ 2 การกำหนด constraint ด้วย VFL ให้กับ view มากกว่า 1 view

โจทย์ก็คือเราจะแปะ redView และ yellowView ลงไปบน superview แบบนี้



เมื่อเราต้องการอ้างถึง yellowView ใน VFL ก็แปลว่าเราต้องแก้ไข viewDictionary ใหม่ดังนี้


และกำหนดขนาดและตำแหน่งได้ดังนี้



เราจะเห็นว่าคราวนี้ VFL ที่ใช้กำหนดตำแหน่งให้ yellowView ดูซับซ้อนมากขึ้นกว่าเดิม แต่ก็ยังสามารถทำความเข้าใจได้อย่างตรงไปตรงมา

ถ้าลองอ่าน @"H:|-20-[redView]-10-[yellowView]" แบบตรงๆ ก็จะได้ความหมายว่า ให้ redView อยู่ห่างจากทางซ้าย 20 และ yellowView อยู่ห่างไปทางขวาของ redView อีก 10

และถ้าอ่าน @"V:|-30-[redView]-40-[yellowView]" ก็จะได้ว่าให้ redView อยู่ห่างจากด้านบน 30 และ yellowView อยู่ห่างจากด้านล่างของ redView ไป 40

ตัวอย่างที่ 3 กำหนด constraint ด้วย VFL โดยกำหนด alignment option

โจทย์คือกำหนดให้ redView และ yellowView มีด้านบนเสมอกันแบบนี้



การใช้ alignment option มาช่วยระบุพิกัด


พบว่าเราไม่จำเป็นต้องสนใจการระบุตำแหน่งในระนาบ vertical ของ yellowView ใน VFL แล้ว ยกหน้าที่นี้ให้ alighment option แทน

ตัวอย่างที่ 4 การใช้และกำหนด matrics ร่วมกับ VFL

การกำหนด matric ก็คือการกำหนดค่าตัวเลขต่างๆ ด้วยชื่อเรียก ทำให้เราไม่จำเป็นต้องจำตัวเลขต่างๆ ที่จะใข้ใน VFL แถมยังแก้ไขค่าตัวเลขต่างๆ ได้ง่ายจากที่เดียวอีกด้วย ดังนี้

จากตัวอย่างจะเห็นว่าใน VFL เราไม่ได้ระบุเป็นตัวเลขอีกแล้ว แต่ใช้ค่าใน matrics ที่เรานิยามขึ้นมาแทน

ตัวอย่างที่ 5 Dynamic Size

ความเจ๋งอย่างหนึ่งของ Auto Layout ก็คือการกำหนด layout ได้แบบ dynamic กรณีนี้ก็คือเราไม่จำเป็นต้องระบุขนาดให้กับ view ก็ได้ แต่ให้มันรักษาระยะห่างระหว่างขอบกับ superview ตาม constraint อย่างเดียวก็พอ การทำแบบนี้มีผลให้ขนาดของ view เปลี่ยนไปด้วยเพื่อรักษา constraint

ลองเช็คผลลัพธ์ด้วยการเปลี่ยน orientation ของ device ดู

ตัวอย่างที่ 6 กำหนด constraint ผ่าน relation

อีกหนึ่งความเจ๋งจริงๆ ของ Auto Layout ก็คือการกำหนด constraint ให้ view โดยอ้างอิงหรือเปรียบเทียบกับค่าของ view อื่นๆ ได้ ซึ่งเราจะกำหนดผ่าน method ของคลาส NSAutoLayoutConstraint ที่หน้าตายาวๆ แบบนี้

ด้วย method นี้จะทำให้เราสามารถกำหนด constraint แนวๆ นี้ได้: "yellowView มีขนาดเป็นครึ่งหนึ่งของ redView และยังอยู่ตรงกลาง redView อีกด้วย"

โจทย์คือต้องการทำแบบนี้

ให้เริ่มจากกำหนด constraint ให้กับ redView ด้วยวิธีเดียวกับในตัวอย่างที่ 5 ก่อน
และด้วย method ที่ว่ามาเราก็สามารถกำหนด ขนาด ให้กับ yellowView ได้แบบนี้

ดูแล้วเหมือนจะเข้าใจได้ยาก แต่ถ้าดูดีๆ ก็สามารถเข้าใจได้ง่าย ถ้าลองอ่านดูตรงๆ จะสามารถอ่านได้ว่า "กำหนดให้ความกว้างของ yellowView เท่ากับ ความกว้างของ redView คูณกับ 0.5 และบวกเพิ่มไปอีก 0.0"

ต่อไปก็กำหนดว่าจุดศูนย์กลางของ yellowView ต้องอยู่จุดเดียวกับจุดศูนย์กลางของ redView ได้แบบนี้
เช่นเดียวกับการกำหนดขนาด เราสามารถอ่านได้อย่างตรงไปตรงมาว่า "จุดศูนย์กลาง X ของ yellowView เท่ากับ จุดศูนย์กลาง X ของ redView คูณ 1.0 และบวกด้วย 0.0"

Bottom Line

นี่เป็นเพียงสรุปแบบสั้นที่สุดเท่าที่จะสั้นได้ของ Auto Layout ที่แท้จริงแล้วรายละเอียดเชิงลึกยังมีมากกว่านี้ และแม้การกำหนด constraint ต่างๆ ผ่าน Xcode มันจะง่ายแสนง่าย และหลายครั้งโค้ดพวกนี้เราไม่จำเป็นต้องเขียนเองเลยด้วยซ้ำ แต่แน่นอนว่าการเข้าใจถึงที่มาที่ไปย่อมทำให้เรารับมือกับปัญหาได้อย่างชาญฉลาดกว่า

อ่านต่อและที่มา