Как сделать UIScrollView масштабировать только в одном направлении при использовании автоматического макета
Я пытаюсь сделать UIScrollView только для масштабирования в горизонтальном направлении. Вид прокрутки настраивается с использованием чистого подхода к автоопределению. Обычный подход, как было предложено в ряде ответов (например, этот) и other. Я успешно использовал это в приложениях до того, как существует автоопределение. Этот подход выглядит следующим образом: в представлении содержимого просмотра прокрутки я переопределяю setTransform()
и изменяю переданный в преобразовании по шкале в направлении x:
override var transform: CGAffineTransform {
get { return super.transform }
set {
var t = newValue
t.d = 1.0
super.transform = t
}
}
Я также reset смещение содержимого прокрутки, чтобы оно не прокручивалось по вертикали во время масштабирования:
func scrollViewDidZoom(scrollView: UIScrollView) {
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0)
}
Это работает очень хорошо, если вы не используете автозапуск:
![Масштабирование без автозапуска]()
Однако при использовании автоотключения смещение содержимого в процессе масштабирования оказывается неправильным. Пока просмотр содержимого масштабируется только в горизонтальном направлении, он перемещается вертикально:
![Масштабирование с автоопределением]()
Я привел пример проекта в GitHub (используемый для создания видео в этом вопросе). Он содержит две раскадровки, Main.storyboard и NoAutolayout.storyboard, которые идентичны, за исключением того, что Main.storyboard включает автозапуск, а NoAutolayout - нет. Переключайтесь между ними, изменяя настройку проекта основного интерфейса, и вы можете видеть поведение в обоих случаях.
Я бы предпочел не отключать автозапуск, так как он решает ряд проблем с реализацией моего пользовательского интерфейса намного лучше, чем это требуется при ручной компоновке. Есть ли способ сохранить правильное вертикальное смещение содержимого (то есть, ноль) во время масштабирования с автозапуском?
ИЗМЕНИТЬ: Я добавил третий образец раскадровки AutolayoutVariableColumns.storyboard к образцу проекта. Это добавляет текстовое поле для изменения количества столбцов в GridView, что изменяет его собственный размер содержимого. Это более подробно показывает поведение, которое мне нужно в реальном приложении, которое вызвало этот вопрос.
Ответы
Ответ 1
Думаю, я понял это. Вам необходимо применить перевод в преобразовании для смещения центрирования, который UIScrollView пытается делать во время масштабирования.
Добавьте это в свой GridView:
var unzoomedViewHeight: CGFloat?
override func layoutSubviews() {
super.layoutSubviews()
unzoomedViewHeight = frame.size.height
}
override var transform: CGAffineTransform {
get { return super.transform }
set {
if let unzoomedViewHeight = unzoomedViewHeight {
var t = newValue
t.d = 1.0
t.ty = (1.0 - t.a) * unzoomedViewHeight/2
super.transform = t
}
}
}
Чтобы вычислить преобразование, нам нужно знать неограниченную высоту представления. Здесь я просто хватаю размер кадра во время layoutSubviews() и предполагаю, что он содержит необработанную высоту. Вероятно, есть некоторые крайние случаи, когда это неверно; может быть лучшим местом в цикле обновления представления для вычисления высоты, но это основная идея.
Ответ 2
Попробуйте установить translatesAutoresizingMaskIntoConstraints
func scrollViewDidZoom(scrollView: UIScrollView) {
gridView.translatesAutoresizingMaskIntoConstraints = true
}
В образце проекта он работает. Если этого недостаточно, попробуйте создать новые ограничения программным способом в scrollViewDidEndZooming:
, возможно, поможет.
Кроме того, если это не поможет, обновите образец проекта, чтобы мы могли воспроизвести проблему с переменной intrinsicContentSize()
Эта статья Ole Begemann мне очень помогла
Как я научился прекращать волнение и любовь Cocoa Автомакет
WWDC 2015 видео
Тайны автоматического макета, часть 1
Тайны автоматического макета, часть 2
И, к счастью, для этого есть флаг. Он называется translatesAutoResizingMask IntoConstraints [без пробела].
Это немного глоток, но он в значительной степени делает то, что он говорит. Это делает представления так, как они выглядели в системе устаревшего макета, но в мире автоматического макета.
Ответ 3
Несмотря на то, что решение @Anna работает, UIScrollView обеспечивает способ работы с автоматической компоновкой. Но поскольку представления прокрутки работают несколько иначе, чем другие представления, ограничения интерпретируются по-разному:
- Ограничения между ребрами/полями прокрутки и его содержанием прикрепляются к экрану прокрутки область содержимого.
- Ограничения между высотой, шириной или центрами прикрепляются к просмотру прокрутки фрейм.
- Ограничения между просмотром прокрутки и представлениями вне прокрутки работают как обычный вид.
Итак, когда вы добавляете subview в представление прокрутки, прикрепленное к его краям/полям, это подвью становится областью содержимого прокрутки или представлением содержимого.
Apple предлагает следующий подход в Работа со списками прокрутки:
- Добавить представление прокрутки в сцену.
- Ограничения рисования для определения размера и положения прокрутки, как обычно.
- Добавить представление в список прокрутки. Установите метку вида Xcode для представления содержимого.
- Соедините представления содержимого сверху, снизу, переднего и заднего краев с прокручивающимися представлениями соответствующих ребер. Теперь представление содержимого определяет область содержимого прокрутки.
- (...)
- (...)
- Разложите содержимое прокрутки в пределах содержимого. Используйте ограничения для размещения содержимого внутри представления содержимого как обычно.
В вашем случае GridView должен находиться внутри представления содержимого:
![Interface Builder]()
Вы можете сохранить ограничения вида сетки, которые вы использовали, но теперь привязаны к представлению содержимого. И для горизонтального масштабирования, сохраните код в точности так, как есть. Ваш transform
управляющий дескриптор очень хорошо.
Ответ 4
Я не уверен, что это удовлетворяет вашему требованию 100% автоотключения, но здесь одно решение, где UIScrollView
задано с автозапуском и gridView
добавлено в качестве подпрограммы к нему.
Ваш ViewController.swift
должен выглядеть следующим образом:
// Add an outlet for the scrollView in interface builder.
@IBOutlet weak var scrollView: UIScrollView!
// Remove the outlet and view for gridView.
//@IBOutlet var gridView: GridView!
// Create the gridView here:
var gridView: GridView!
// Setup some views
override func viewDidLoad() {
super.viewDidLoad()
// The width for the gridView is set to 1000 here, change if needed.
gridView = GridView(frame: CGRect(x: 0, y: 0, width: 1000, height: self.view.bounds.height))
scrollView.addSubview(gridView)
scrollView.contentSize = CGSize(width: gridView.frame.width, height: scrollView.frame.height)
gridView.contentMode = .ScaleAspectFill
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return gridView
}
func scrollViewDidZoom(scrollView: UIScrollView) {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0)
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
}