Cocoa: jaka jest różnica między kadrem a granicami?

UIView wszystkie jego podklasy mają właściwości frame i bounds. Co za różnica?

Author: mk12, 2009-07-31

11 answers

Granice UIView to prostokąt, wyrażony jako położenie (x,y) i rozmiar (szerokość,wysokość) względem własnego układu współrzędnych (0,0).

Ramka zUIView jest prostokątem , wyrażonym jako położenie (x,y) i rozmiar (szerokość,wysokość) względem superview, w którym jest zawarty.

Wyobraźmy sobie więc widok, który ma rozmiar 100x100 (szerokość x wysokość) ustawiony na 25,25 (X, y) swojego superwizora. Następujące kod wyświetla granice i ramkę tego widoku:

// This method is in the view controller of the superview
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"bounds.origin.x: %f", label.bounds.origin.x);
    NSLog(@"bounds.origin.y: %f", label.bounds.origin.y);
    NSLog(@"bounds.size.width: %f", label.bounds.size.width);
    NSLog(@"bounds.size.height: %f", label.bounds.size.height);

    NSLog(@"frame.origin.x: %f", label.frame.origin.x);
    NSLog(@"frame.origin.y: %f", label.frame.origin.y);
    NSLog(@"frame.size.width: %f", label.frame.size.width);
    NSLog(@"frame.size.height: %f", label.frame.size.height);
}

A wyjście tego kodu to:

bounds.origin.x: 0
bounds.origin.y: 0
bounds.size.width: 100
bounds.size.height: 100

frame.origin.x: 25
frame.origin.y: 25
frame.size.width: 100
frame.size.height: 100

Widzimy więc, że w obu przypadkach szerokość i wysokość widoku są takie same niezależnie od tego, czy patrzymy na granice czy ramkę. Czym się różni pozycjonowanie X, y widoku. W przypadku granic współrzędne x i y są równe 0,0, ponieważ te współrzędne są względem samego widoku. Jednak współrzędne ramki x i Y są względem pozycja widoku w widoku nadrzędnym (o którym wcześniej mówiliśmy, że wynosi 25,25).

Istnieje również świetna prezentacja , która obejmuje widoki. Zobacz slajdy 1-20, które nie tylko wyjaśniają różnicę między ramkami i granicami, ale także pokazują przykłady wizualne.

 917
Author: shek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-28 19:36:47

Krótka Odpowiedź

Ramka = lokalizacja i rozmiar widoku przy użyciu układu współrzędnych widoku nadrzędnego

  • Ważne dla: umieszczenie widoku w rodzicu

Bounds = położenie i rozmiar widoku za pomocą własnego układu współrzędnych

  • Ważne dla: umieszczania zawartości lub podwidywań widoku w sobie

Szczegółowa Odpowiedź

Aby pomóc mi zapamiętać frame , I pomyśl o ramce do zdjęć na ścianie. Ramka obrazu jest jak granica widoku. Mogę powiesić obraz, gdzie tylko zechcę na ścianie. W ten sam sposób mogę umieścić widok w dowolnym miejscu wewnątrz widoku nadrzędnego (zwanego także superview). Widok rodzica jest jak ściana. Początek układu współrzędnych w systemie iOS to lewy górny róg. Możemy umieścić nasz widok na początku superview ustawiając współrzędne x-y ramki widoku na (0, 0), co jest jak powieszenie naszego obrazu na samej górze lewy róg ściany. Aby przesunąć w prawo, zwiększ x, aby przesunąć w dół zwiększ y.

Aby pomóc mi zapamiętać granice , myślę o boisku do koszykówkigdzie czasami koszykówka dostaje knocked out of bounds. Drybling piłkę na całym boisku do koszykówki, ale naprawdę nie obchodzi cię, gdzie sam sąd jest. Może to być na siłowni, na zewnątrz w szkole średniej lub przed Twoim domem. Nieważne. Chcesz po prostu grać w kosza. W tak samo, układ współrzędnych dla granic widoku dba tylko o sam widok. Nie wie nic o tym, gdzie znajduje się widok w widoku nadrzędnym. Początek obwiedni (punkt (0, 0) domyślnie) jest lewym górnym rogiem widoku. Wszelkie podglądy tego widoku są określone w odniesieniu do tego punktu. To jest jak biorąc koszykówkę do przedniego lewego rogu sądu.

Teraz pojawia się zamieszanie, gdy próbujesz porównać ramki i granice. Właściwie to nie jest tak źle jak wydaje się, że na początku. Użyjmy kilku zdjęć, aby pomóc nam zrozumieć.

Frame vs Bounds

Na pierwszym obrazku po lewej stronie mamy widok, który znajduje się w lewym górnym rogu widoku nadrzędnego. żółty prostokąt reprezentuje ramkę widoku. po prawej stronie widzimy widok ponownie, ale tym razem widok nadrzędny nie jest pokazany. To dlatego, że granice nie wiedzą o widoku rodzica. zielony prostokąt reprezentuje granice widoku. The red kropka na obu obrazach reprezentuje pochodzenie ramki lub obramowania.

Frame
    origin = (0, 0)
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Tutaj wpisz opis obrazka

Więc rama i granice były dokładnie takie same na tym zdjęciu. Spójrzmy na przykład, gdzie są różne.

Frame
    origin = (40, 60)  // That is, x=40 and y=60
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Tutaj wpisz opis obrazka

Więc widać, że zmiana współrzędnych x-y ramki przesuwa ją w widoku nadrzędnym. Ale treść samego widoku nadal wygląda dokładnie tak samo. Bounds nie mają pojęcia, że wszystko jest inne.

Do tej pory szerokość i wysokość zarówno ramki, jak i obrzeży były dokładnie takie same. Ale to nie zawsze prawda. Zobacz, co się stanie, jeśli obrócimy widok o 20 stopni zgodnie z ruchem wskazówek zegara. (Obrót odbywa się za pomocą przekształceń. Więcej informacji można znaleźć w dokumentacji oraz w poniższym widoku i przykładach warstw .)

Frame
    origin = (20, 52)  // These are just rough estimates.
    width = 118
    height = 187

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Tutaj wpisz opis obrazka

Widać, że granice są nadal takie same. Nadal nie wiedzą, że coś się stało! Ramka wartości się jednak zmieniły.

Teraz jest trochę łatwiej dostrzec różnicę między ramką a obramowaniem, prawda? W artykule prawdopodobnie nie rozumiesz ramek i granic definiuje ramkę widoku jako

...najmniejszą obwiednię tego widoku w odniesieniu do jego rodziców układ współrzędnych, w tym wszelkie przekształcenia zastosowane do tego widoku.

Należy pamiętać, że jeśli przekształcisz widok, ramka stanie się undefined. Tak naprawdę żółta ramka, którą narysowałem wokół obróconych zielonych granic na powyższym obrazku, nigdy nie istnieje. Oznacza to, że jeśli obracasz, skalujesz lub wykonujesz inną transformację, nie powinieneś już używać wartości ramki. Nadal można jednak używać wartości obwiedni. Apple docs ostrzega:

Ważne: jeśli właściwość widoku transform nie zawiera transformacji tożsamości, ramka tego widoku jest niezdefiniowana, a więc wyniki its autoresistowskie zachowania.

Raczej niefortunne z powodu autoresizacji.... Jest jednak coś, co możesz zrobić.

Apple docs stan:

Podczas modyfikowania właściwości transform twojego widoku, wszystkie transformacje są wykonywane względem punktu centralnego widok.

Więc jeśli musisz przenieść widok w rodzicu po wykonaniu transformacji, możesz to zrobić, zmieniając współrzędne view.center. Jak frame, center używa układu współrzędnych widoku nadrzędnego.

Ok, pozbądźmy się rotacji i skupmy się na granicach. Do tej pory granice pochodzenia zawsze pozostawały na (0, 0). Ale nie musi. Co zrobić, jeśli nasz Widok ma duży subview, który jest zbyt duży, aby wyświetlić wszystkie na raz? Zrobimy to UIImageView z dużym obrazem. Oto nasze drugie zdjęcie z góry, ale tym razem możemy zobaczyć, jak wyglądałaby cała zawartość subview naszego widoku na przykład.
Frame
    origin = (40, 60)
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Tutaj wpisz opis obrazka

Tylko lewy górny róg obrazu może zmieścić się w granicach widoku. Zobacz, co się stanie, jeśli zmienimy współrzędne.

Frame
    origin = (40, 60)
    width = 80
    height = 130

Bounds 
    origin = (280, 70)
    width = 80
    height = 130

Tutaj wpisz opis obrazka

Ramka nie została przesunięta w widoku superview, ale zawartość wewnątrz ramki uległa zmianie, ponieważ początek prostokąta granic zaczyna się od innej części widoku. To jest cała idea UIScrollView i to są podklasy(na przykład, a UITableView). Zobacz też zrozumienie UIScrollView aby uzyskać więcej wyjaśnień.

Kiedy używać ramki i kiedy używać granic

Ponieważ frame odnosi się do lokalizacji widoku nadrzędnego, używasz go podczas dokonywania zmian zewnętrznych, takich jak zmiana jego szerokości lub Znajdowanie odległości między widokiem a szczytem widoku nadrzędnego.

Użyj bounds podczas dokonywania zmian wewnętrznych , takich jak rysowanie rzeczy lub porządkowanie podwidrzeń w widoku. Korzystaj również z ograniczeń aby uzyskać rozmiar widoku, jeśli zrobiłeś na nim transfomację.

Artykuły do dalszych badań:

Apple docs

Powiązane pytania dotyczące StackOverflow

Inne zasoby

Ćwicz siebie

Oprócz czytania powyższych artykułów, bardzo pomaga mi zrobić test app. Możesz spróbować zrobić coś podobnego. (Pomysł zaczerpnąłem z tego kursu wideo, ale niestety nie jest on darmowy.)

Tutaj wpisz opis obrazka

Oto kod w celach informacyjnych:

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var myView: UIView!

    // Labels
    @IBOutlet weak var frameX: UILabel!
    @IBOutlet weak var frameY: UILabel!
    @IBOutlet weak var frameWidth: UILabel!
    @IBOutlet weak var frameHeight: UILabel!
    @IBOutlet weak var boundsX: UILabel!
    @IBOutlet weak var boundsY: UILabel!
    @IBOutlet weak var boundsWidth: UILabel!
    @IBOutlet weak var boundsHeight: UILabel!
    @IBOutlet weak var centerX: UILabel!
    @IBOutlet weak var centerY: UILabel!
    @IBOutlet weak var rotation: UILabel!

    // Sliders
    @IBOutlet weak var frameXSlider: UISlider!
    @IBOutlet weak var frameYSlider: UISlider!
    @IBOutlet weak var frameWidthSlider: UISlider!
    @IBOutlet weak var frameHeightSlider: UISlider!
    @IBOutlet weak var boundsXSlider: UISlider!
    @IBOutlet weak var boundsYSlider: UISlider!
    @IBOutlet weak var boundsWidthSlider: UISlider!
    @IBOutlet weak var boundsHeightSlider: UISlider!
    @IBOutlet weak var centerXSlider: UISlider!
    @IBOutlet weak var centerYSlider: UISlider!
    @IBOutlet weak var rotationSlider: UISlider!

    // Slider actions
    @IBAction func frameXSliderChanged(sender: AnyObject) {
        myView.frame.origin.x = CGFloat(frameXSlider.value)
        updateLabels()
    }
    @IBAction func frameYSliderChanged(sender: AnyObject) {
        myView.frame.origin.y = CGFloat(frameYSlider.value)
        updateLabels()
    }
    @IBAction func frameWidthSliderChanged(sender: AnyObject) {
        myView.frame.size.width = CGFloat(frameWidthSlider.value)
        updateLabels()
    }
    @IBAction func frameHeightSliderChanged(sender: AnyObject) {
        myView.frame.size.height = CGFloat(frameHeightSlider.value)
        updateLabels()
    }
    @IBAction func boundsXSliderChanged(sender: AnyObject) {
        myView.bounds.origin.x = CGFloat(boundsXSlider.value)
        updateLabels()
    }
    @IBAction func boundsYSliderChanged(sender: AnyObject) {
        myView.bounds.origin.y = CGFloat(boundsYSlider.value)
        updateLabels()
    }
    @IBAction func boundsWidthSliderChanged(sender: AnyObject) {
        myView.bounds.size.width = CGFloat(boundsWidthSlider.value)
        updateLabels()
    }
    @IBAction func boundsHeightSliderChanged(sender: AnyObject) {
        myView.bounds.size.height = CGFloat(boundsHeightSlider.value)
        updateLabels()
    }
    @IBAction func centerXSliderChanged(sender: AnyObject) {
        myView.center.x = CGFloat(centerXSlider.value)
        updateLabels()
    }
    @IBAction func centerYSliderChanged(sender: AnyObject) {
        myView.center.y = CGFloat(centerYSlider.value)
        updateLabels()
    }
    @IBAction func rotationSliderChanged(sender: AnyObject) {
        let rotation = CGAffineTransform(rotationAngle: CGFloat(rotationSlider.value))
        myView.transform = rotation
        updateLabels()
    }

    private func updateLabels() {

        frameX.text = "frame x = \(Int(myView.frame.origin.x))"
        frameY.text = "frame y = \(Int(myView.frame.origin.y))"
        frameWidth.text = "frame width = \(Int(myView.frame.width))"
        frameHeight.text = "frame height = \(Int(myView.frame.height))"
        boundsX.text = "bounds x = \(Int(myView.bounds.origin.x))"
        boundsY.text = "bounds y = \(Int(myView.bounds.origin.y))"
        boundsWidth.text = "bounds width = \(Int(myView.bounds.width))"
        boundsHeight.text = "bounds height = \(Int(myView.bounds.height))"
        centerX.text = "center x = \(Int(myView.center.x))"
        centerY.text = "center y = \(Int(myView.center.y))"
        rotation.text = "rotation = \((rotationSlider.value))"

    }

}
 675
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-10 05:12:34

Spróbuj uruchomić poniższy kod

- (void)viewDidLoad {
    [super viewDidLoad];
    UIWindow *w = [[UIApplication sharedApplication] keyWindow];
    UIView *v = [w.subviews objectAtIndex:0];

    NSLog(@"%@", NSStringFromCGRect(v.frame));
    NSLog(@"%@", NSStringFromCGRect(v.bounds));
}

Wyjście tego kodu to:

Orientacja urządzenia jest portretowa

{{0, 0}, {768, 1024}}
{{0, 0}, {768, 1024}}

Orientacja urządzenia Case jest pozioma

{{0, 0}, {768, 1024}}
{{0, 0}, {1024, 768}}

Oczywiście widać różnicę między ramką a obramowaniem

 43
Author: tristan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-04 01:44:21

Ramka jest prostokątem, który definiuje UIView z względem superview .

Granice rect to zakres wartości, które definiują układ współrzędnych NSView.

Czyli wszystko w tym prostokącie będzie faktycznie wyświetlane w UIView.

 13
Author: jorik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-07 18:21:19

Ramka jest początkiem (lewy górny róg) i rozmiarem widoku w układzie współrzędnych Super view , co oznacza, że można przetłumaczyć widok w super view zmieniając pochodzenie ramki, granice z drugiej strony to rozmiar i pochodzenie we własnym układzie współrzędnych, więc domyślnie początek granic wynosi (0,0).

Większość czasu ramka i granice są zgodne , ale jeśli masz widok ramki ((140,65),(200,250)) i granice ((0,0), (200,250)) na przykład i Widok został przechylony tak, że stoi w prawym dolnym rogu, wtedy granice nadal będą ((0,0),(200,250)) , ale rama nie jest .

Tutaj wpisz opis obrazka

Ramka będzie najmniejszym prostokątem, który otacza Widok,więc ramka (jak na zdjęciu) będzie ((140,65),(320,320)).

Inną różnicą jest na przykład, jeśli masz superView, którego granice są ((0,0), (200,200)) i ten superView ma subView, którego ramka jest ((20,20),(100,100)) i zmieniłeś Superwizjer do ((20,20),(200,200)) , następnie ramka subView będzie nadal ((20,20), (100,100)), ale przesunięta o (20,20), ponieważ jego układ współrzędnych superview został przesunięty o (20,20).

Mam nadzieję, że to komuś pomoże.

 11
Author: m.eldehairy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-06 19:13:14

Frame its relative to its SuperView, podczas gdy Bounds relative to its NSView.

Przykład: X = 40, Y=60.Zawiera również 3 widoki.Ten Diagram pokazuje jasny pomysł.

Ramka

BOUNDS

 5
Author: RAGHUNATH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-30 01:02:13

Dodam moje 5 centów.

Ramka jest używana przez Widok nadrzędny, aby umieścić ją wewnątrz widoku nadrzędnego.

Bounds jest używany przez sam widok do umieszczania własnej zawartości(podobnie jak widok przewijania robi to podczas przewijania). Zobacz także clipsToBounds . Ograniczenia mogą być również używane do powiększania / pomniejszania zawartości widoku.

Analogia:
Ramka ~ ekran TV
Bounds ~ Camera (zoom, move, rotate)

 5
Author: Serg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-13 13:56:29

Wszystkie powyższe odpowiedzi są poprawne i to jest moje zdanie na ten temat:

Aby odróżnić pojęcia ramki od granic programista powinien przeczytać:

  1. w stosunku do superview (widok rodzica) jest zawarty w = FRAME
  2. względem własnego układu współrzędnych, określa jego położenie podrzędne = granice

"granice" jest mylące, ponieważ sprawia wrażenie, że współrzędne są pozycją widoku, dla którego jest ustawiona. Ale są one w relacjach i dostosowane do stałych ramek.

ekran iPhone

 4
Author: Marina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-17 19:37:55

Powyższe odpowiedzi bardzo dobrze wyjaśniły różnicę między obramowaniami a obramowaniami.

Granice: rozmiar i położenie widoku według własnego układu współrzędnych.

Frame : rozmiar i lokalizacja widoku względem SuperView.

Wtedy jest zamieszanie, że w przypadku granic X, Y zawsze będzie "0". to nieprawda. Można to zrozumieć również w UIScrollView i UICollectionView.

Gdy X, y nie są 0.
Załóżmy, że mamy UIScrollView. Wdrożyliśmy paginację. UIScrollView ma 3 strony, a jego szerokość ContentSize jest trzykrotnie większa niż szerokość ekranu (Załóżmy, że szerokość ekranu wynosi 320). Wysokość jest stała (Załóżmy 200).

scrollView.contentSize = CGSize(x:320*3, y : 200)

Dodaj trzy widoki UIImageView jako podview i miej oko na x wartość ramki

let imageView0 = UIImageView.init(frame: CGRect(x:0, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
let imageView1 :  UIImageView.init( frame: CGRect(x:320, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
let imageView2 :  UIImageView.init(frame: CGRect(x:640, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
scrollView.addSubview(imageView0)
scrollView.addSubview(imageView0)
scrollView.addSubview(imageView0)
  1. Strona 0: Gdy ScrollView znajduje się na stronie 0, granice będą (x:0,y:0, width : 320, height: : 200)

  2. Strona 1: Przewiń i przejdź do strony 1.
    Teraz granice będą (x:320,y:0, width : 320, height : 200) Pamiętaj, że powiedzieliśmy w odniesieniu do własnego układu współrzędnych. Tak więc teraz "widoczna część" naszego przewijania ma " x " na poziomie 320. Spójrz na ramkę imageView1.

  3. Strona 2: Przewiń i przejdź do strony 2 {x:640,y:0, width : 320, height : 200) Jeszcze raz spójrz na ramkę imageView2

To samo dotyczy przypadku UICollectionView. Najprostszym sposobem na obejrzenie collectionView jest przewinięcie go i wydrukowanie / zapisanie jego granic, a otrzymasz pomysł.

 2
Author: Muhammad Nayab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-10 03:08:39

Bounds - x:0, y:0, width: 20, height: 40 is a static
Ramka - x:60, y:20, szerokość: 45, wysokość: 45 jest dynamiką opartą na granicach wewnętrznych.

Jeszcze jedna ilustracja pokazująca różnicę między ramką a obramowaniem. W tym przykładzie:

  • View B jest podzbiorem View A
  • View B został przeniesiony do x:60, y: 20
  • View B został obrócony 45 degrees

Tutaj wpisz opis obrazka

 2
Author: yoAlex5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-20 20:36:55

Frame vs bounds

  • jeśli utworzysz Widok W x:0, Y:0, szerokość:400, wysokość: 400, jego ramka a granice są takie same.
  • Jeśli przeniesiesz ten widok Do x:400, jego ramka będzie odzwierciedlać tę zmianę, ale jej granice nie będą. Pamiętaj, że granice są w stosunku do własnej przestrzeni widoku, a wewnętrznie do widoku nic się nie zmieniło.
  • jeśli przekształcisz widok, np. obracając go lub skalując, ramka zmieni się, aby odzwierciedlić to, ale granice nadal nie będą – jak far as widok jest wewnętrznie zaniepokojony, nie zmienił się.
  • jeśli zmienisz obramowanie, zmieni to zawartość wewnątrz ramki, ponieważ początek prostokąta obramowania zaczyna się od inna część widoku.
 0
Author: Anit Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-08 14:34:56