2016-10-25 4 views
1

У меня проблема с изменением размера вертикального окна, работающим с прокруткой в ​​режиме автоматического макета.Как изменить размер NSWindow прямо в мире автомакетов

Что я Хочу

Я хотел бы повторить, насколько это возможно, текущее окно моего приложения в изменении размеров поведения. Ширина окна является гибкой, но высота окна обычно должна отслеживать высоту содержимого. В частности:

  1. Обычно, окно автоматически изменяет свою высоту, чтобы точно соответствовать
    его содержимое (за исключением # 2).
  2. Пользователь может вручную изменить размер окна таким образом, чтобы он был меньше его содержимого , и в этом случае вложенное представление прокрутки будет прокручивать содержимое . После того, как оно сделано короче, окно остается такой, пока вручную не изменится размер , или содержимое снова станет равным или меньше, чем окно.
  3. Если содержимое растет до такой степени, что нижняя часть окна сталкивается с границами док-станции или экрана, высота окна привязана к этой высоте так же, как если бы пользователь изменил ее размер.

Что у меня есть

Я создал окно с набором представительных подвидов, которые имитируют основные потребности моего приложения. иерархия окна проста:

Window 
    NSView (contentView) 
     NSScrollView 
      NSClipView (NSScrollView.contentView) 
       NSView (NSScrollView.documentView) 
        A bunch of standard and custom subviews with constraints 

Вы можете загрузить тестовый проект здесь (MacOS 10,12/Xcode 8): http://mbx.cm/t/4FUGY

Я организовал различные подвидов, так что они имеют гибкую ширину, но ограничения определите ровно одну возможную высоту. Родительский вид прокрутки заполняет представление содержимого окна.

Автомашина отлично работает. Окно автоматически изменяется в соответствии с размером содержимого. Если высота содержимого изменяется, высота окна меняется на совпадение. Потрясающие.

Что я не могу получить, чтобы работать

у меня не было никакой удачи получать NSWindow позволить мне вручную изменить его высоту. Индикатор изменения размера (при наведении курсора по краю) показывает, что я могу изменить ширину окна, но не его высоту.

Первоначально я думал: «О, это должно быть приоритетом сопротивления сжатию в одном из видов прокрутки». Но я не могу найти сочетание сопротивления сжатию или обнимать приоритеты, которые изменят это поведение. Я попытался установить приоритеты самого представления прокрутки и документа documentView (что не имеет для меня никакого смысла, но я все равно пытался). Я пробовал значения 749, 499, 49 и 1 в каждой комбинации, о которой я мог думать.

Я искал каждую проблему, которая, казалось, была выпущена для этого, но большинство размещенных вопросов, казалось, решали различные проблемы.

Я добавил кнопку «Дамп» для регистрации вертикальных ограничений. Все перечисленное выглядит как ожидалось, за исключением нескольких объектов NSAutoresizingMaskLayoutConstraint, которые я не понимаю.Однако они представляют собой ограничения между представлением документа и представлением клипа, поэтому я предполагаю, что они автоматически создаются и не являются частью проблемы.

<NSLayoutConstraint:0x608000082580 PartBoxView:0x608000140840'Part B'.height == 96 (active)> 
<NSLayoutConstraint:0x608000083de0 V:[PartBoxView:0x608000140840'Part B']-(0)-| (active, names: '|':NSView:0x6080001212c0)> 
<NSLayoutConstraint:0x608000083e80 V:[PartBoxView:0x608000140370'Part A']-(NSSpace(8))-[PartBoxView:0x608000140840'Part B'] (active)> 
<NSLayoutConstraint:0x608000082da0 V:|-(0)-[NSScrollView:0x6080001c10e0] (active, names: '|':NSView:0x608000121400)> 
<NSLayoutConstraint:0x608000083430 V:|-(0)-[NSView:0x6080001212c0] (active, names: '|':NSClipView:0x10040e2a0)> 
<NSLayoutConstraint:0x608000081e00 V:[NSScrollView:0x6080001c10e0]-(0)-| (active, names: '|':NSView:0x608000121400)> 
<NSAutoresizingMaskLayoutConstraint:0x650000082b20 h=-&- v=-&- NSView:0x608000121400.minY == 0 (active, names: '|':NSThemeFrame:0x102504ea0'Window')> 
<NSAutoresizingMaskLayoutConstraint:0x6500000823f0 h=-&- v=-&- NSClipView:0x10040e2a0.minY == 1 (active, names: '|':NSScrollView:0x6080001c10e0)> 
<NSLayoutConstraint:0x608000083480 V:[NSView:0x6080001212c0]-(0)-| (active, names: '|':NSClipView:0x10040e2a0)> 
<NSAutoresizingMaskLayoutConstraint:0x650000082440 h=-&- v=-&- V:[NSClipView:0x10040e2a0]-(1)-| (active, names: '|':NSScrollView:0x6080001c10e0)> 
<NSLayoutConstraint:0x608000083d40 V:|-(0)-[PartBoxView:0x608000140370'Part A'] (active, names: '|':NSView:0x6080001212c0)> 
<NSLayoutConstraint:0x608000083c50 V:[NSImageView:0x608000161bc0]-(20)-| (active, names: '|':PartBoxView:0x608000140370'Part A')> 
<NSLayoutConstraint:0x608000083bb0 V:|-(20)-[NSImageView:0x608000161bc0] (active, names: '|':PartBoxView:0x608000140370'Part A')> 
<NSLayoutConstraint:0x608000083660 NSImageView:0x608000161bc0.height == 64 (active)> 

Я надеюсь, что кто-то с автомастером может рассказать мне, как получить № 2. Я почти уверен, что # 3 потребует какой-то пользовательский код, но №2 - это большой барьер.

Фоновой

Я нахожусь в процессе подачи моего приложения крупной подтяжки лица. Это довольно большое приложение, поэтому я начал с создания набора тестовых проектов для тестирования некоторых новых пользовательских интерфейсов и методов.

Первое крупное предприятие превращает все в автомат. Большинство из них похоже, что это будет довольно гладко, и я с нетерпением жду некоторых из многих преимуществ автоматической компоновки.

ответ

1

Нет полностью автоматизированного способа достижения вашей цели # 2. Вы хотите два разных режима. В одном режиме окно всегда делается достаточно большим для размещения контента. Если он растет, окно растет; если он сжимается, окно сжимается. В другом режиме, окно разрешено быть меньше, чем требуется, чтобы соответствовать содержимому, потому что пользователь изменил его таким образом. В этом случае, если контент растет, окно не растет; если он сжимается, окно не сжимается, если содержимое не становится настолько маленьким, что все подходит, а затем переключается обратно в первый режим.

Автоматическая компоновка на самом деле не делает такие режимы, как это, по крайней мере, не автоматически. Вам придется обнаруживать изменения режима и программно изменять ограничения для реализации двух режимов поведения.

Вы, очевидно, создали ограничения между видом документа и видом клипа, чтобы совместить их вершины и днища. Это существенно заставляет просмотр клипа, а затем вид прокрутки будет размером с вид документа. Прокрутка никогда не будет прокручиваться, потому что она никогда не будет слишком маленькой, чтобы показать весь контент.

Я думаю, вы можете потребовать два ограничения для нижнего интервала. Одним из ограничений будет неравенство при требуемом (1000) приоритете. Вы хотите выразить, что просмотр клипа никогда не должен быть больше, чем просмотр документа. Нижняя часть документа может быть больше или равна нижней части клипа, но не меньше.

Вторым ограничением нижнего интервала будет равенство (с константой 0, как сейчас), но с приоритетом чуть меньше NSLayoutPriorityWindowSizeStayPut (500). Это означает, что вы хотите, чтобы просмотр клипа и просмотр прокрутки были достаточно большими, чтобы соответствовать содержимому, если только это не заставит окно расти или не позволит пользователю сжать его.

Проблема в том, что если окно достаточно велико, чтобы соответствовать содержимому, а затем контент растет, это не будет заставлять окно расти. То, что я описал, реализует второй режим.

Вы можете попытаться реализовать первый режим, установив приоритет второго ограничения выше. Проблема в том, что пользователю не будет разрешено изменять размер окна. Вы вернулись к своей нынешней ситуации.

Что я думаю, что вам нужно будет сделать, это заметить, когда содержимое изменяет размер, наблюдая вид документа NSViewFrameDidChangeNotification. Обязательно сообщите об этом в представлении, установив для свойства postsFrameChangedNotifications значение true. Когда кадр изменится, если вы считаете, что должен находиться в первом режиме, установите приоритет второго ограничения, вызовите -layoutIfNeeded в окне, а затем установите приоритет вниз.Я думаю, вам, возможно, придется отложить настройку приоритета до следующего поворота цикла событий, потому что не ясно, что вы получите уведомление после просмотра клипа, поэтому, возможно, используйте GCD, чтобы запланировать это.

Итак, откуда вы знаете, в каком режиме вы должны быть? Я не совсем уверен. I думаю, что будет работать для делегата окна (часто его контроллера), чтобы реализовать -windowDidEndLiveResize:, чтобы узнать, когда пользователь закончил изменение размера окна. Я думаю, что пользователь, изменяющий размер, будет живым изменением размера, в то время как программно изменяет его размер или изменяет размер авто макета, этого не будет.

Если это пользователь, который изменил размер окна, вам необходимо узнать, выросло ли пользователь в окне, чтобы весь контент соответствовал или размер пользователя был меньше этого. Для этого вы можете сравнить высоту границ документа с видом клипа documentVisibleRect.

+0

Кен, Спасибо за обстоятельный и вдумчивый ответ. –

1

Кен,

Спасибо за обстоятельный и вдумчивый ответ.

Вы, по-видимому, создали ограничения между видом документа и видом клипа, чтобы совместить их вершины и днища.

Ну, я не сделал, но IB конечно сделал. ;)

Итак, первым шагом было отредактировать ограничения просмотра клипов, изменив ограничение clipView.bottom-0-documentView.bottom от «равно 0» до «меньше или равно 0». Это позволяет видеть клип (по вертикали) меньше, чем просмотр документа, что в конечном итоге позволяет пользователю изменять размер окна по вертикали.

Затем я начал с ваших других предложений, добавив некоторые дополнительные ограничения, чтобы прикрепить высоту к документу и либо изменить его свойство active, либо изменить его priority.

В конечном счете, однако, я пошел несколько иначе. Проблема в том, что когда вы просите содержимое окна расти много, или когда оно близко к нижней части экрана, его поведение ... ну, странно.

Вместо этого я создал «липкий» режим для окна. Когда он установлен, а вид документа растет, я вручную вычисляю новый кадр для окна. Я делаю это, потому что я могу контролировать, как изменяется размер окна, когда он находится рядом с нижней и/или верхней частью экрана.

Предупреждение

я обнаружил трудный путь, что есть скрытая опасность для всех этих методов. NSViewFrameDidChangeNotification отправляется всякий раз, когда размер кадра изменяется. Это может произойти во время автоматический макет. Если вы заметили это уведомление и сразу же отрегулировали размер окна, размер содержимого или ограничения, автомат очень сильно расстроен и выдает неприятные «круговые» и «рекурсивные» предупреждения о макетах (он также иногда не может правильно изменить размер). Решение состояло в том, чтобы просто обернуть исправление размера окна в блоке и поставить его в очередь на выполнение в основном потоке, после того как закончилась логика автоматического макета.

Вот готовый, рабочий, тестовый проект (с комментариями и примечаниями): http://mbx.cm/t/Zjdml

Вот соответствующий код:

@interface ViewController() 
{ 
    BOOL windowSizeSticky;  // a change in the content size should resize the window to match 
} 

- (void)documentSizeChangedNotification:(NSNotification*)notification; 

@end 


@implementation ViewController 

- (void)dealloc 
{ 
    self.view.window.delegate = nil; 
    [[NSNotificationCenter defaultCenter] removeObserver:self]; 
} 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // Enable the document view to post size change notifications 
    NSView* docView = self.scrollView.documentView; 
    docView.postsFrameChangedNotifications = YES; 
    // Subscribe to those changes 
    [[NSNotificationCenter defaultCenter] addObserver:self 
         selector:@selector(documentSizeChangedNotification:) 
         name:NSViewFrameDidChangeNotification 
         object:docView]; 
    // Queue up an initial evaluation so windowSizeSticky is set correctly 
    dispatch_async(dispatch_get_main_queue(), ^{ 
    [self documentSizeChangedNotification:nil]; 
    }); 
} 

- (void)viewWillAppear 
{ 
    // Make this controller the window's delegate 
    self.view.window.delegate = self; 

    [super viewWillAppear]; 
} 

- (void)windowDidEndLiveResize:(NSNotification *)notification 
{ 
    // Whenever the user resizes the window, reevaluate the windowSizeSticky mode 
    NSView* documentView = self.scrollView.documentView; 
    NSClipView* clipView = (NSClipView*)(documentView.superview); 
    NSRect docVisible = clipView.documentVisibleRect; 
    NSRect docFrame = documentView.frame; 

    // Update the "sticky" mode depending on whether the window now displays all, or only a portion, of the contents 
    windowSizeSticky = (docVisible.size.height==docFrame.size.height); 
} 

- (void)documentSizeChangedNotification:(__unused NSNotification *)notification 
{ 
    NSView* documentView = self.scrollView.documentView; 
    NSWindow* window = documentView.window; 
    if (!window.inLiveResize) // Suppress this logic while the user is manually resizing the window 
    { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // Do this the next time the main loop is idle 
     // This notification can be sent during auto layout, and we don't want to attempt to resize 
     // the window in the middle of an auto layout calculation. 

     // The geometry of the document view has changed; check to see if the window needs resizing 
     NSClipView* clipView = (NSClipView*)(documentView.superview); 
     NSRect docVisible = clipView.documentVisibleRect; 
     NSRect docFrame = documentView.frame; // The doc's frame is in the clip view's coordinate system 
     if (docVisible.size.height==docFrame.size.height) 
     { 
     // All of the document is (vertically) visible in the clip view 
     // That means the window is displaying all of its contents 
     // Whenever this happens, switch to "sticky" mode so future changes in content will make the window grow 
     windowSizeSticky = YES; 
     } 
     else if (windowSizeSticky && docVisible.size.height < docFrame.size.height) 
     { 
     // The content is now taller than the view port of the scroll view & the window is "sticky" 
     // Try to make the window taller so all of the content is exposed 
     NSRect windowFrame = window.frame; 
     CGFloat addHeight = docFrame.size.height-docVisible.size.height; 

     NSRect contentRect = [window contentRectForFrameRect:windowFrame]; 
     contentRect.size.height += addHeight; 

     // Calculate an ideal window frame, then adjust the existing frame so it's as close as we can get 
     NSRect targetFrame = [window frameRectForContentRect:contentRect]; 
     CGFloat deltaY = targetFrame.size.height-windowFrame.size.height; 
     if (deltaY >= 1.0) 
      { 
      // The window needs to be taller 
      // Make it tall enough to display all of the content, keeping its title bar where it is 
      windowFrame.origin.y -= deltaY; 
      windowFrame.size.height += deltaY; 

      // Screen bounds check... 
      NSRect visibleFrame = window.screen.visibleFrame; 
      if (visibleFrame.origin.y>windowFrame.origin.y) 
      { 
      // The bottom of the window is now below the visible area of the screen 
      // Move the whole window up so it's back on the screen 
      windowFrame.origin.y = visibleFrame.origin.y; 
      if (visibleFrame.origin.y+visibleFrame.size.height < windowFrame.origin.y+windowFrame.size.height) 
       { 
       // The top of the window is now off the top of the screen 
       // Shorten the window so it's entirely within the screen 
       windowFrame.size.height = visibleFrame.size.height; 
       // This also means "sticky" mode is off, since we had to size the window to something smaller 
       // than its contents. 
       windowSizeSticky = NO; 
       } 
      } 
      [window setFrame:windowFrame 
       display:NO 
       animate:NO/* be consistent; constraints doesn't animate when getting shorter */]; 
      } 
     } 
     // else { window is not sticky OR its contents doesn't exceed the height of the window: do nothing } 
     }); 
    } 
} 

@end 
+0

Для записи, когда я пошел, чтобы свернуть это в свой проект, в конечном итоге было более разумно реализовать эту логику на оконном контроллере, а не на контроллере представления. –

+0

Этот ответ работает для меня, я должен изменить размер ячейки наложения в соответствии с размером внешнего вида, который изменился при изменении размера окна. Событие WindowDidResize и WindowDidEndLiveresize работает, но иногда не отвечает на результат последнего размера. Но это работает хорошо. Спасибо – Adison

Смежные вопросы