具有匹配宽度 DocumentView 的 NSScrollView
NSScrollView with a matching width DocumentView
首先,我想说的是,我已经查看了大量其他资源来尝试实现此目的,但我所查看的内容似乎都无济于事。
例如:Automatically grow document view of NSScrollView using auto layout?
我有一个 NSScrollView,它是我在 Interface Builder 中创建的,并设置了约束以填充它所在的 window:滚动视图的顶部、左侧、右侧和底部设置为等于顶部、左侧、父视图的右侧和底部(这是 Xamarin 中 viewcontroller 使用的 xib 视图)。
我想要的文档视图是一个简单的 NSView,它在我的 viewcontroller 的 ViewDidLoad:
中动态填充了 NSImageViews
EventListScrollView.TranslatesAutoresizingMaskIntoConstraints = false;
var documentView = new NSView(EventListScrollView.Bounds);
//documentView.AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.HeightSizable;
//documentView.TranslatesAutoresizingMaskIntoConstraints = false;
NSView lastHeader = null;
foreach(var project in _projects)
{
var imageView = new NSImageView();
imageView.TranslatesAutoresizingMaskIntoConstraints = false;
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Horizontal);
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Vertical);
imageView.ImageScaling = NSImageScale.ProportionallyUpOrDown;
var xPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Left, 1, 0);
NSLayoutConstraint yPosConstraint = null;
if(lastHeader!=null)
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
}
else
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Top, 1, 0);
}
var widthConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Width, 1, 0);
var heightConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, imageView, NSLayoutAttribute.Width, (nfloat)0.325, 0);
documentView.AddSubview(imageView);
documentView.AddConstraints(new[] { xPosConstraint, yPosConstraint, widthConstraint, heightConstraint });
lastHeader = imageView;
ImageService.Instance.LoadUrl($"https:{project.ProjectLogo}").Into(imageView);
}
EventListScrollView.DocumentView = documentView;
因为我不一定知道 _projects
中有多少项目,而且我不知道将加载到每个 imageView
中的图像的高度,尽管我知道预期比例,直到运行时我才知道 documentView 的高度。
我希望每次包含 window 滚动视图时 documentView
的宽度都调整为与 NSClipView
相同(因此也是滚动视图)调整大小。至于 documentView
的高度,我希望它由我在 documentView
中填充的 imageView
的大小决定,这就是为什么高度约束是相对于宽度。
我玩过 documentView
的 AutoresizingMask
、NSClipView
、滚动视图等。我尝试将 TranslatesAutoresizingMaskIntoConstraints
设置为 false
的所有内容并添加约束以将 documentView
的左、右和上设置为等于 NSClipView
的左、右和上。当我这样做时,视图似乎甚至没有出现。我似乎无法弄清楚这一点。
我也试过在不使用 NSImageViews 的情况下这样做:
public override void ViewDidLoad()
{
base.ViewDidLoad();
EventListScrollView.TranslatesAutoresizingMaskIntoConstraints = false;
var clipView = new NSClipView
{
TranslatesAutoresizingMaskIntoConstraints = false
};
EventListScrollView.ContentView = clipView;
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Left, 1, 0));
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Right, 1, 0));
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Top, 1, 0));
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Bottom, 1, 0));
var documentView = new NSView();
documentView.WantsLayer = true;
documentView.Layer.BackgroundColor = NSColor.Black.CGColor;
documentView.TranslatesAutoresizingMaskIntoConstraints = false;
EventListScrollView.DocumentView = documentView;
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Left, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Right, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Top, 1, 0));
NSView lastHeader = null;
foreach(var project in _projects)
{
var random = new Random();
var imageView = new NSView();
imageView.TranslatesAutoresizingMaskIntoConstraints = false;
imageView.WantsLayer = true;
imageView.Layer.BackgroundColor = NSColor.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)).CGColor;
var xPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Left, 1, 0);
NSLayoutConstraint yPosConstraint = null;
if(lastHeader!=null)
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
}
else
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Top, 1, 0);
}
var widthConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Width, 1, 0);
var heightConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, imageView, NSLayoutAttribute.Width, (nfloat)0.325, 0);
documentView.AddSubview(imageView);
documentView.AddConstraints(new[] { xPosConstraint, yPosConstraint, widthConstraint, heightConstraint });
lastHeader = imageView;
}
}
我明白了。所以,这样做的诀窍是 documentView 不会知道自己有多大,除非你把它的底部固定到你的最后一个子视图上。您可以使用视觉格式来执行此操作,但如果您直到运行时才真正知道您的视图是什么,那就太过分了。这是对我有用的代码:
public override void ViewDidLoad()
{
base.ViewDidLoad();
var clipView = EventListScrollView.ContentView;
var documentView = new FlippedView();
documentView.TranslatesAutoresizingMaskIntoConstraints = false;
EventListScrollView.DocumentView = documentView;
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Left, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Right, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Top, 1, 0));
NSView lastHeader = null;
foreach (var project in _projects)
{
var imageView = new NSImageView();
imageView.TranslatesAutoresizingMaskIntoConstraints = false;
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Horizontal);
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Vertical);
imageView.ImageScaling = NSImageScale.ProportionallyUpOrDown;
var xPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Left, 1, 0);
NSLayoutConstraint yPosConstraint = null;
if (lastHeader != null)
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
}
else
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Top, 1, 0);
}
var widthConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Width, 1, 0);
var heightConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Height, NSLayoutRelation.LessThanOrEqual, imageView, NSLayoutAttribute.Width, (nfloat)0.360, 0);
documentView.AddSubview(imageView);
documentView.AddConstraint(xPosConstraint);
documentView.AddConstraint(yPosConstraint);
documentView.AddConstraint(widthConstraint);
documentView.AddConstraint(heightConstraint);
lastHeader = imageView;
ImageService.Instance.LoadUrl($"https:{project.ProjectLogo}").Into(imageView);
}
if(lastHeader!=null)
{
var bottomPinConstraint = NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
documentView.AddConstraint(bottomPinConstraint);
}
}
关键是最后一个约束——bottomPinConstraint
。这允许 documentView
扩展其高度以适应所有子视图。
此外,即使我手动设置 NSClipView
和 documentView
之间的约束,我仍然需要使用带有 IsFlipped = true;
的视图,否则它会忽略约束并将我的视图固定到 NSClipView
的底部。我的 EventListScrollView 是在 Interface Builder 中创建的,并设置了约束以使其填满它所在的整个 window。
最终结果是一个 NSScrollView 和一个 DocumentView,它填充了 ScrollView 的宽度,但也垂直扩展以适应我以编程方式添加到它的子视图。
我这样做是为了让我的自定义视图根据滚动视图宽度调整大小。
scrollView.documentView = customView
customView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
customView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor),
])
自定义视图在 X 轴上提供 intrinsicContentSize
。
eonil 的回答几乎是完美的,但是如果系统设置为始终显示滚动条,偏移量就会不正确。水平滚动条以一个小的偏移结束。要解决此问题,请改用剪辑视图的锚点:
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor),
view.widthAnchor.constraint(equalTo: scrollView.contentView.widthAnchor),
])
首先,我想说的是,我已经查看了大量其他资源来尝试实现此目的,但我所查看的内容似乎都无济于事。
例如:Automatically grow document view of NSScrollView using auto layout?
我有一个 NSScrollView,它是我在 Interface Builder 中创建的,并设置了约束以填充它所在的 window:滚动视图的顶部、左侧、右侧和底部设置为等于顶部、左侧、父视图的右侧和底部(这是 Xamarin 中 viewcontroller 使用的 xib 视图)。
我想要的文档视图是一个简单的 NSView,它在我的 viewcontroller 的 ViewDidLoad:
中动态填充了 NSImageViewsEventListScrollView.TranslatesAutoresizingMaskIntoConstraints = false;
var documentView = new NSView(EventListScrollView.Bounds);
//documentView.AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.HeightSizable;
//documentView.TranslatesAutoresizingMaskIntoConstraints = false;
NSView lastHeader = null;
foreach(var project in _projects)
{
var imageView = new NSImageView();
imageView.TranslatesAutoresizingMaskIntoConstraints = false;
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Horizontal);
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Vertical);
imageView.ImageScaling = NSImageScale.ProportionallyUpOrDown;
var xPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Left, 1, 0);
NSLayoutConstraint yPosConstraint = null;
if(lastHeader!=null)
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
}
else
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Top, 1, 0);
}
var widthConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Width, 1, 0);
var heightConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, imageView, NSLayoutAttribute.Width, (nfloat)0.325, 0);
documentView.AddSubview(imageView);
documentView.AddConstraints(new[] { xPosConstraint, yPosConstraint, widthConstraint, heightConstraint });
lastHeader = imageView;
ImageService.Instance.LoadUrl($"https:{project.ProjectLogo}").Into(imageView);
}
EventListScrollView.DocumentView = documentView;
因为我不一定知道 _projects
中有多少项目,而且我不知道将加载到每个 imageView
中的图像的高度,尽管我知道预期比例,直到运行时我才知道 documentView 的高度。
我希望每次包含 window 滚动视图时 documentView
的宽度都调整为与 NSClipView
相同(因此也是滚动视图)调整大小。至于 documentView
的高度,我希望它由我在 documentView
中填充的 imageView
的大小决定,这就是为什么高度约束是相对于宽度。
我玩过 documentView
的 AutoresizingMask
、NSClipView
、滚动视图等。我尝试将 TranslatesAutoresizingMaskIntoConstraints
设置为 false
的所有内容并添加约束以将 documentView
的左、右和上设置为等于 NSClipView
的左、右和上。当我这样做时,视图似乎甚至没有出现。我似乎无法弄清楚这一点。
我也试过在不使用 NSImageViews 的情况下这样做:
public override void ViewDidLoad()
{
base.ViewDidLoad();
EventListScrollView.TranslatesAutoresizingMaskIntoConstraints = false;
var clipView = new NSClipView
{
TranslatesAutoresizingMaskIntoConstraints = false
};
EventListScrollView.ContentView = clipView;
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Left, 1, 0));
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Right, 1, 0));
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Top, 1, 0));
EventListScrollView.AddConstraint(NSLayoutConstraint.Create(clipView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, EventListScrollView, NSLayoutAttribute.Bottom, 1, 0));
var documentView = new NSView();
documentView.WantsLayer = true;
documentView.Layer.BackgroundColor = NSColor.Black.CGColor;
documentView.TranslatesAutoresizingMaskIntoConstraints = false;
EventListScrollView.DocumentView = documentView;
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Left, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Right, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Top, 1, 0));
NSView lastHeader = null;
foreach(var project in _projects)
{
var random = new Random();
var imageView = new NSView();
imageView.TranslatesAutoresizingMaskIntoConstraints = false;
imageView.WantsLayer = true;
imageView.Layer.BackgroundColor = NSColor.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)).CGColor;
var xPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Left, 1, 0);
NSLayoutConstraint yPosConstraint = null;
if(lastHeader!=null)
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
}
else
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Top, 1, 0);
}
var widthConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Width, 1, 0);
var heightConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, imageView, NSLayoutAttribute.Width, (nfloat)0.325, 0);
documentView.AddSubview(imageView);
documentView.AddConstraints(new[] { xPosConstraint, yPosConstraint, widthConstraint, heightConstraint });
lastHeader = imageView;
}
}
我明白了。所以,这样做的诀窍是 documentView 不会知道自己有多大,除非你把它的底部固定到你的最后一个子视图上。您可以使用视觉格式来执行此操作,但如果您直到运行时才真正知道您的视图是什么,那就太过分了。这是对我有用的代码:
public override void ViewDidLoad()
{
base.ViewDidLoad();
var clipView = EventListScrollView.ContentView;
var documentView = new FlippedView();
documentView.TranslatesAutoresizingMaskIntoConstraints = false;
EventListScrollView.DocumentView = documentView;
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Left, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Right, 1, 0));
clipView.AddConstraint(NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, clipView, NSLayoutAttribute.Top, 1, 0));
NSView lastHeader = null;
foreach (var project in _projects)
{
var imageView = new NSImageView();
imageView.TranslatesAutoresizingMaskIntoConstraints = false;
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Horizontal);
imageView.SetContentCompressionResistancePriority(1, NSLayoutConstraintOrientation.Vertical);
imageView.ImageScaling = NSImageScale.ProportionallyUpOrDown;
var xPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Left, 1, 0);
NSLayoutConstraint yPosConstraint = null;
if (lastHeader != null)
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
}
else
{
yPosConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Top, 1, 0);
}
var widthConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, documentView, NSLayoutAttribute.Width, 1, 0);
var heightConstraint = NSLayoutConstraint.Create(imageView, NSLayoutAttribute.Height, NSLayoutRelation.LessThanOrEqual, imageView, NSLayoutAttribute.Width, (nfloat)0.360, 0);
documentView.AddSubview(imageView);
documentView.AddConstraint(xPosConstraint);
documentView.AddConstraint(yPosConstraint);
documentView.AddConstraint(widthConstraint);
documentView.AddConstraint(heightConstraint);
lastHeader = imageView;
ImageService.Instance.LoadUrl($"https:{project.ProjectLogo}").Into(imageView);
}
if(lastHeader!=null)
{
var bottomPinConstraint = NSLayoutConstraint.Create(documentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, lastHeader, NSLayoutAttribute.Bottom, 1, 0);
documentView.AddConstraint(bottomPinConstraint);
}
}
关键是最后一个约束——bottomPinConstraint
。这允许 documentView
扩展其高度以适应所有子视图。
此外,即使我手动设置 NSClipView
和 documentView
之间的约束,我仍然需要使用带有 IsFlipped = true;
的视图,否则它会忽略约束并将我的视图固定到 NSClipView
的底部。我的 EventListScrollView 是在 Interface Builder 中创建的,并设置了约束以使其填满它所在的整个 window。
最终结果是一个 NSScrollView 和一个 DocumentView,它填充了 ScrollView 的宽度,但也垂直扩展以适应我以编程方式添加到它的子视图。
我这样做是为了让我的自定义视图根据滚动视图宽度调整大小。
scrollView.documentView = customView
customView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
customView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor),
])
自定义视图在 X 轴上提供 intrinsicContentSize
。
eonil 的回答几乎是完美的,但是如果系统设置为始终显示滚动条,偏移量就会不正确。水平滚动条以一个小的偏移结束。要解决此问题,请改用剪辑视图的锚点:
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor),
view.widthAnchor.constraint(equalTo: scrollView.contentView.widthAnchor),
])