รับส่วนที่ปลอดภัยสูงและต่ำ


178

ใน iPhone X ใหม่สิ่งที่จะเป็นวิธีที่เหมาะสมที่สุดในการรับทั้งความสูงและความสูงด้านล่างสำหรับพื้นที่ที่ไม่ปลอดภัย

ป้อนคำอธิบายรูปภาพที่นี่

คำตอบ:


366

ลองสิ่งนี้:

ในวัตถุประสงค์ C

if (@available(iOS 11.0, *)) {
    UIWindow *window = UIApplication.sharedApplication.keyWindow;
    CGFloat topPadding = window.safeAreaInsets.top;
    CGFloat bottomPadding = window.safeAreaInsets.bottom;
}

ในสวิฟท์

if #available(iOS 11.0, *) {
    let window = UIApplication.shared.keyWindow
    let topPadding = window?.safeAreaInsets.top
    let bottomPadding = window?.safeAreaInsets.bottom
}

1
@KrutikaSonawala bottomPadding + 10 (ค่าของคุณ)
6788419

12
ดูเหมือนว่านี่จะไม่เหมาะกับฉัน - พิมพ์ค่าของ UIApplication.shared.keyWindow? .safeAreaInsets คืนค่า 0s ทั้งหมด ความคิดใด ๆ
Hai

2
ฉันทำฉันสามารถ จำกัด ต่อ safeAreaLayoutGuides ในมุมมองใด ๆ แต่พิมพ์ค่าของ safeAreaInsets ของหน้าต่างคีย์โดยตรงเพียงส่งกลับศูนย์ rect นี่เป็นปัญหาเนื่องจากฉันต้อง จำกัด มุมมองที่ถูกเพิ่มลงในหน้าต่างคีย์ด้วยตนเอง แต่ไม่ได้อยู่ในลำดับชั้นการดูของตัวควบคุมมุมมองรูท
Hai

6
สำหรับฉันkeyWindowเป็นเสมอnilดังนั้นฉันเปลี่ยนเป็นwindows[0]และลบออก?จากการผูกมัดตัวเลือกแล้วมันทำงาน
George_E

2
มันจะทำงานเฉพาะเมื่อมุมมองของตัวควบคุมปรากฏขึ้นแล้ว
Vanya

85

ในการรับความสูงระหว่างเส้นบอกแนวโครงร่างที่คุณเพิ่งทำ

let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height

ดังนั้นfull frame height = 812.0,safe area height = 734.0

ด้านล่างเป็นตัวอย่างที่มุมมองสีเขียวมีกรอบ guide.layoutFrame

ป้อนคำอธิบายรูปภาพที่นี่


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

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

2
จริงๆแล้ว @ user6788419 คำตอบก็ดูดีเหมือนกันและสั้นลงเรื่อย ๆ
Tulleb

ฉันมีความสูง 812.0 ด้วยรหัสนั้นโดยใช้มุมมองของคอนโทรลเลอร์ ดังนั้นฉันใช้UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrameซึ่งจะมีกรอบที่ปลอดภัย
Ferran Maylinch

1
@FerranMaylinch ไม่แน่ใจว่าคุณกำลังตรวจสอบที่ความสูง แต่ฉันมี 812 ซึ่งกลายเป็นเนื่องจากการตรวจสอบค่าก่อนที่จะมองเห็นมุมมอง ในกรณีดังกล่าวความสูงจะเหมือนกัน "หากมุมมองไม่ได้ติดตั้งในลำดับชั้นมุมมองหรือยังไม่ปรากฏบนหน้าจอขอบของตัวเลย์เอาต์จะเท่ากับขอบของมุมมอง" developer.apple.com/documentation/ uikit / uiview / …
Nicholas Harlen

81

สวิฟต์ 4, 5

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

สิ่งนี้จะเสียบเข้ากับตัวควบคุมมุมมองใด ๆ :

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let topSafeArea: CGFloat
    let bottomSafeArea: CGFloat

    if #available(iOS 11.0, *) {
        topSafeArea = view.safeAreaInsets.top
        bottomSafeArea = view.safeAreaInsets.bottom
    } else {
        topSafeArea = topLayoutGuide.length
        bottomSafeArea = bottomLayoutGuide.length
    }

    // safe area values are now available to use
}

ฉันชอบวิธีนี้ที่จะทำให้มันออกไปนอกหน้าต่าง (ถ้าเป็นไปได้) เพราะมันเป็นวิธีที่ API ได้รับการออกแบบและที่สำคัญกว่านั้นคือค่าที่ได้รับการปรับปรุงในระหว่างการเปลี่ยนแปลงมุมมองทั้งหมดเช่นการเปลี่ยนแนวอุปกรณ์

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

anyLifecycleMethod()
    guard let root = UIApplication.shared.keyWindow?.rootViewController else {
        return
    }
    let topSafeArea: CGFloat
    let bottomSafeArea: CGFloat

    if #available(iOS 11.0, *) {
        topSafeArea = root.view.safeAreaInsets.top
        bottomSafeArea = root.view.safeAreaInsets.bottom
    } else {
        topSafeArea = root.topLayoutGuide.length
        bottomSafeArea = root.bottomLayoutGuide.length
    }

    // safe area values are now available to use
}

3
กรุณาประกาศโดยใช้ let topSafeArea: CGFloat แทน!
Gusutafu

หลีกเลี่ยงการใช้viewDidLayoutSubviews(ซึ่งสามารถเรียกได้หลายครั้ง) นี่เป็นวิธีแก้ปัญหาที่จะทำงานได้แม้ในviewDidLoad: stackoverflow.com/a/53864017/7767664
user924

@ user924 แล้วค่าจะไม่ได้รับการปรับปรุงเมื่อปลอดภัยการเปลี่ยนแปลงพื้นที่ (เช่นแถบสถานะดับเบิลสูง)
BSOD

@bsod แล้วนี่จะดีกว่าstackoverflow.com/a/3420550/7767664 (เพราะเป็นเพียงแถบสถานะและไม่ใช่มุมมองอื่น ๆ )
user924

หรือแทนที่จะผ่านตัวแทนแอพคุณก็สามารถใช้ API พื้นที่ปลอดภัยที่ Apple สร้างไว้ในตัวควบคุมมุมมอง
BSOD

21

ไม่มีคำตอบอื่นที่นี่ใช้ได้สำหรับฉัน แต่สิ่งนี้ทำได้

var topSafeAreaHeight: CGFloat = 0
var bottomSafeAreaHeight: CGFloat = 0

  if #available(iOS 11.0, *) {
    let window = UIApplication.shared.windows[0]
    let safeFrame = window.safeAreaLayoutGuide.layoutFrame
    topSafeAreaHeight = safeFrame.minY
    bottomSafeAreaHeight = window.frame.maxY - safeFrame.maxY
  }

1
โค้ดของคุณจะทำงานในส่วนใด ๆ ของวงจรชีวิตตัวควบคุมมุมมอง ถ้าคุณต้องการใช้ view.safeAreaInsets.top คุณต้องตรวจสอบให้แน่ใจว่าคุณเรียกมันหลังจากใน viewDidAppear หรือใหม่กว่า ใน viewDidLoad คุณจะไม่ได้รับ view.safeAreaInsets.top ด้วยค่าที่ถูกต้องเนื่องจากยังไม่ได้กำหนดค่าเริ่มต้น
user3204765

1
คำตอบที่ดีที่สุดจนถึงวันนี้
Rikco

17

คำตอบทั้งหมดที่นี่มีประโยชน์ขอขอบคุณทุกคนที่ให้ความช่วยเหลือ

อย่างไรก็ตามฉันเห็นว่าหัวข้อพื้นที่ปลอดภัยค่อนข้างสับสนเล็กน้อยซึ่งดูเหมือนจะไม่ได้รับการบันทึกไว้อย่างดี

ดังนั้นฉันจะสรุปได้ที่นี่เป็นข้าวต้มที่เป็นไปได้ที่จะทำให้มันง่ายต่อการเข้าใจsafeAreaInsets, และsafeAreaLayoutGuideLayoutGuide

ใน iOS 7, Apple เปิดตัวtopLayoutGuideและbottomLayoutGuideคุณสมบัติในUIViewController, พวกเขาอนุญาตให้คุณสร้างข้อ จำกัด เพื่อป้องกันเนื้อหาของคุณจากการถูกซ่อนโดยแถบ UIKit เช่นสถานะ, การนำทางหรือแถบแท็บมันเป็นไปได้ด้วยคำแนะนำเค้าโครงเหล่านี้เพื่อระบุข้อ จำกัด เกี่ยวกับเนื้อหา ซ่อนโดยองค์ประกอบการนำทางด้านบนหรือด้านล่าง (แถบ UIKit แถบสถานะแถบนำทางหรือแถบแท็บ…)

ตัวอย่างเช่นหากคุณต้องการให้ tableView เริ่มต้นจากหน้าจอด้านบนคุณได้ทำสิ่งนั้น:

self.tableView.contentInset = UIEdgeInsets(top: -self.topLayoutGuide.length, left: 0, bottom: 0, right: 0)

ใน iOS 11 Apple ได้เลิกใช้คุณสมบัติเหล่านี้แทนที่ด้วยแนวทางแบบพื้นที่ปลอดภัยเดียว

ปลอดภัยตามพื้นที่ของ Apple

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

ด้านล่างพื้นที่ปลอดภัยถูกเน้นใน iPhone 8 และ iPhone X-series:

ป้อนคำอธิบายรูปภาพที่นี่

safeAreaLayoutGuideเป็นทรัพย์สินของUIView

ในการรับความสูงของsafeAreaLayoutGuide:

extension UIView {
   var safeAreaHeight: CGFloat {
       if #available(iOS 11, *) {
        return safeAreaLayoutGuide.layoutFrame.size.height
       }
       return bounds.height
  }
}

นั่นจะคืนความสูงของลูกศรในรูปภาพของคุณ

ทีนี้สิ่งที่เกี่ยวกับการทำให้ตัวบ่งชี้ "บาก" และหน้าจอหลักด้านบนสูงขึ้น

ที่นี่เราจะใช้ safeAreaInsets

พื้นที่ปลอดภัยของมุมมองสะท้อนพื้นที่ที่ไม่ครอบคลุมโดยแถบนำทางแถบแท็บแถบเครื่องมือและบรรพบุรุษอื่น ๆ ที่บดบังมุมมองของตัวควบคุมมุมมอง (ใน tvOS พื้นที่ปลอดภัยจะแสดงพื้นที่ที่ไม่ได้อยู่ในกรอบของหน้าจอ) คุณได้รับพื้นที่ที่ปลอดภัยสำหรับมุมมองโดยใช้ส่วนเสริมในคุณสมบัตินี้กับขอบเขตของมุมมอง หากมุมมองไม่ได้ถูกติดตั้งในลำดับชั้นมุมมองหรือยังไม่ปรากฏบนหน้าจอขอบของสิ่งที่ใส่เข้าไปในคุณสมบัตินี้จะเป็น 0

ต่อไปนี้จะแสดงพื้นที่ที่ไม่ปลอดภัยและมีระยะห่างจากขอบบน iPhone 8 และหนึ่งใน iPhone X-Series

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ทีนี้ถ้าเพิ่มแถบนำทาง

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นตอนนี้วิธีรับความสูงของพื้นที่ที่ไม่ปลอดภัยหรือไม่ เราจะใช้safeAreaInset

นี่คือวิธีแก้ไขปัญหา แต่สิ่งที่สำคัญก็คือ

คนแรก:

self.view.safeAreaInsets

ที่จะคืนค่า EdgeInsets ตอนนี้คุณสามารถเข้าถึงด้านบนและด้านล่างเพื่อทราบสิ่งที่ใส่เข้าไป

อันที่สอง:

UIApplication.shared.windows.first{$0.isKeyWindow }?.safeAreaInsets

อันแรกที่คุณกำลังดู insets ดังนั้นหากมีแถบนำทางมันจะถูกพิจารณา แต่อันที่สองคุณกำลังเข้าถึง safeAreaInsets ของหน้าต่างดังนั้นแถบนำทางจะไม่ถูกนำมาพิจารณา


5

ใน iOS 11 มีวิธีการหนึ่งที่บอกว่า safeArea เปลี่ยนไปเมื่อใด

override func viewSafeAreaInsetsDidChange() {
    super.viewSafeAreaInsetsDidChange()
    let top = view.safeAreaInsets.top
    let bottom = view.safeAreaInsets.bottom
}

2

ฉันกำลังทำงานกับกรอบ CocoaPods และในกรณีที่UIApplication.sharedเป็นไม่พร้อมใช้งานแล้วฉันจะใช้safeAreaInsetsในมุมมองของwindow:

if #available(iOS 11.0, *) {
    let insets = view.window?.safeAreaInsets
    let top = insets.top
    let bottom = insets.bottom
}

2

safeAreaLayoutGuide เมื่อมุมมองปรากฏบนหน้าจอคู่มือนี้จะแสดงส่วนของมุมมองที่ไม่ครอบคลุมโดยแถบนำทางแถบแท็บแถบเครื่องมือและมุมมองบรรพบุรุษอื่น ๆ (ใน tvOS พื้นที่ปลอดภัยแสดงพื้นที่ที่ไม่ได้อยู่ในกรอบของหน้าจอ) หากมุมมองไม่ได้ถูกติดตั้งในลำดับชั้นการดูหรือยังไม่ปรากฏบนหน้าจอ

จากนั้นเพื่อให้ได้ความสูงของลูกศรสีแดงในภาพหน้าจอ:

self.safeAreaLayoutGuide.layoutFrame.size.height

2

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

มันจะให้คำเตือนการคัดค้าน '' keyWindow 'เลิกใช้แล้วใน iOS 13.0: ไม่ควรใช้กับแอพพลิเคชั่นที่รองรับหลาย ๆ ฉากเพราะมันจะส่งคืนหน้าต่างกุญแจข้ามฉากที่เชื่อมต่อทั้งหมด' เนื่องจากฉากเชื่อมต่อ ฉันใช้วิธีนี้

extension UIView {

    var safeAreaBottom: CGFloat {
         if #available(iOS 11, *) {
            if let window = UIApplication.shared.keyWindowInConnectedScenes {
                return window.safeAreaInsets.bottom
            }
         }
         return 0
    }

    var safeAreaTop: CGFloat {
         if #available(iOS 11, *) {
            if let window = UIApplication.shared.keyWindowInConnectedScenes {
                return window.safeAreaInsets.top
            }
         }
         return 0
    }
}

extension UIApplication {
    var keyWindowInConnectedScenes: UIWindow? {
        return windows.first(where: { $0.isKeyWindow })
    }
}

1

Objective-C ที่มีปัญหาเมื่อkeyWindowมีค่าเท่ากับศูนย์ เพียงใส่รหัสด้านบนในviewDidAppear (ไม่ใช่ใน viewDidLoad)



1

ส่วนขยาย Swift 5

สิ่งนี้สามารถใช้เป็นส่วนขยายและเรียกใช้ด้วย: UIApplication.topSafeAreaHeight

extension UIApplication {
    static var topSafeAreaHeight: CGFloat {
        var topSafeAreaHeight: CGFloat = 0
         if #available(iOS 11.0, *) {
               let window = UIApplication.shared.windows[0]
               let safeFrame = window.safeAreaLayoutGuide.layoutFrame
               topSafeAreaHeight = safeFrame.minY
             }
        return topSafeAreaHeight
    }
}

การขยาย UIA เป็นโปรแกรมเสริมสามารถเป็นส่วนเสริมของ UIView หรืออะไรก็ได้ที่ต้องการหรืออาจเป็นฟังก์ชั่นที่ดีกว่า


0

วิธีการโค้งมนมากขึ้น

  import SnapKit

  let containerView = UIView()
  containerView.backgroundColor = .red
  self.view.addSubview(containerView)
  containerView.snp.remakeConstraints { (make) -> Void in
        make.width.top.equalToSuperView()
        make.top.equalTo(self.view.safeArea.top)
        make.bottom.equalTo(self.view.safeArea.bottom)
  }




extension UIView {
    var safeArea: ConstraintBasicAttributesDSL {
        if #available(iOS 11.0, *) {
            return self.safeAreaLayoutGuide.snp
        }
        return self.snp
    }


    var isIphoneX: Bool {

        if #available(iOS 11.0, *) {
            if topSafeAreaInset > CGFloat(0) {
                return true
            } else {
                return false
            }
        } else {
            return false
        }

    }

    var topSafeAreaInset: CGFloat {
        let window = UIApplication.shared.keyWindow
        var topPadding: CGFloat = 0
        if #available(iOS 11.0, *) {
            topPadding = window?.safeAreaInsets.top ?? 0
        }

        return topPadding
    }

    var bottomSafeAreaInset: CGFloat {
        let window = UIApplication.shared.keyWindow
        var bottomPadding: CGFloat = 0
        if #available(iOS 11.0, *) {
            bottomPadding = window?.safeAreaInsets.bottom ?? 0
        }

        return bottomPadding
    }
}

0

สำหรับผู้ที่เปลี่ยนเป็นโหมดแนวนอนคุณต้องแน่ใจว่าใช้viewSafeAreaInsetsDidChangeหลังจากการหมุนเพื่อรับค่าที่อัปเดตมากที่สุด:

private var safeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

override func viewSafeAreaInsetsDidChange() {
        if #available(iOS 11.0, *) {
            safeAreaInsets = UIApplication.shared.keyWindow!.safeAreaInsets
        }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.