表示する実際の画像のサイズに応じて、UIImageView
を拡大または縮小します。ただし、スーパービューのリーディングエッジから垂直に中央に10ポイント離れたままにしたいのです。
ただし、これらの2つの制約を設定した場合、自動レイアウトを満たすのに十分な制約を設定していないと文句を言います。私には、私が望むものを完全に説明しているようです。私は何が欠けていますか?
画像ビューの固有のサイズはすでに画像のサイズに依存しています。あなたの仮定(そして制約は正しい)。
ただし、インターフェイスビルダーで画像ビューを設定し、画像を提供していない場合、レイアウトシステム(インターフェイスビルダー)は、コンパイル時に画像ビューの大きさを認識しません。画像ビューのサイズはさまざまであるため、レイアウトはあいまいです。これがエラーをスローするものです。
画像ビューの画像プロパティを設定すると、画像ビューの固有のサイズは画像のサイズによって定義されます。実行時にビューを設定している場合は、Annaが述べたことを正確に実行し、イメージビューのプロパティインスペクターで「プレースホルダー」固有のサイズをインターフェイスビルダーに提供できます。これは、インターフェイスビルダーに「今はこのサイズを使用します。後で実際のサイズを提供します」と伝えます。プレースホルダー制約は実行時に無視されます。
もう1つのオプションは、画像をインターフェイスビルダーの画像ビューに直接割り当てることです(ただし、画像は動的であるため、これは機能しません)。
ここに重要な問題があります:UIImageView
が自動レイアウトと対話する唯一の方法は、そのintrinsicContentSize
プロパティを介することです。このプロパティは、画像自体の固有のサイズを提供しますが、それ以上のものは提供しません。これにより、さまざまなソリューションを使用して3つの状況が作成されます。
最初のケースでは、外部の自動レイアウト制約がその位置のみに影響するコンテキストにUIImageView
を配置すると、ビューのintrinsicContentSize
は、その画像のサイズにしたいことを宣言します。自動レイアウトは、ビューのサイズを画像のサイズに等しくなるように自動的に変更します。これはまさにあなたが望むものであり、それから人生は良いです!
他の場合には、あなたは別のケースにいます。画像ビューに必要なサイズは正確にわかっていますが、表示された画像のアスペクト比を維持することも必要です。この場合、自動レイアウトを使用して画像のサイズを制限しながら、画像ビューで古いcontentMode
プロパティを.scaleAspectFit
に設定できます。このプロパティは、必要に応じてパディングを追加して、表示された画像を画像ビューで再スケーリングします。そして、これがあなたが望むものであるならば、人生は良いです!
しかし、多くの場合、トリッキーな3番目のケースです。自動レイアウトを使用して、部分的にビューのサイズを決定し、画像のアスペクト比が他の部分を決定できるようにします。たとえば、自動レイアウト制約を使用してサイズの1次元(垂直タイムラインスクロールの幅など)を決定し、ビューに依存して、画像のアスペクト比に一致する高さが必要であることを自動レイアウトに伝えます(スクロール可能なアイテムが短すぎたり高すぎたりすることはありません)。
イメージビューはintrinsicContentSize
のみを通信するため、これを行うために通常のイメージビューを構成することはできません。そのため、自動レイアウトが1つの寸法を制約するコンテキストに画像ビューを配置する場合(たとえば、幅を短くするように制約する)、画像ビューは、高さをタンデムに再スケーリングすることを自動レイアウトに通知しません。画像自体の高さを変更せずに、固有のコンテンツサイズを報告し続けます。
画像ビューを構成して、自動レイアウトに含まれる画像のアスペクト比を取得したいことを伝えるには、その効果に新しい制約を追加する必要があります。さらに、イメージ自体が更新されるたびに、その制約を更新する必要があります。これを行うUIImageView
サブクラスを構築できるため、動作は自動です。以下に例を示します。
public class ScaleAspectFitImageView : UIImageView {
/// constraint to maintain same aspect ratio as the image
private var aspectRatioConstraint:NSLayoutConstraint? = nil
required public init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
self.setup()
}
public override init(frame:CGRect) {
super.init(frame:frame)
self.setup()
}
public override init(image: UIImage!) {
super.init(image:image)
self.setup()
}
public override init(image: UIImage!, highlightedImage: UIImage?) {
super.init(image:image,highlightedImage:highlightedImage)
self.setup()
}
override public var image: UIImage? {
didSet {
self.updateAspectRatioConstraint()
}
}
private func setup() {
self.contentMode = .scaleAspectFit
self.updateAspectRatioConstraint()
}
/// Removes any pre-existing aspect ratio constraint, and adds a new one based on the current image
private func updateAspectRatioConstraint() {
// remove any existing aspect ratio constraint
if let c = self.aspectRatioConstraint {
self.removeConstraint(c)
}
self.aspectRatioConstraint = nil
if let imageSize = image?.size, imageSize.height != 0
{
let aspectRatio = imageSize.width / imageSize.height
let c = NSLayoutConstraint(item: self, attribute: .width,
relatedBy: .equal,
toItem: self, attribute: .height,
multiplier: aspectRatio, constant: 0)
// a priority above fitting size level and below low
c.priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0
self.addConstraint(c)
self.aspectRatioConstraint = c
}
}
}
より多くのコメントは this Gist で利用可能です
@algal case 3の場合、私がしなければならなかったことは
class ScaledHeightImageView: UIImageView {
override var intrinsicContentSize: CGSize {
if let myImage = self.image {
let myImageWidth = myImage.size.width
let myImageHeight = myImage.size.height
let myViewWidth = self.frame.size.width
let ratio = myViewWidth/myImageWidth
let scaledHeight = myImageHeight * ratio
return CGSize(width: myViewWidth, height: scaledHeight)
}
return CGSize(width: -1.0, height: -1.0)
}
}
これにより、intrinsicContentSizeの高さコンポーネントがスケーリングされます。これはスーパークラスのデフォルトの動作であるため、画像がない場合は(-1.0、-1.0)が返されます。
また、画像ビューを含むセルを含むテーブルにUITableViewAutomaticDimension
を設定する必要はなく、セルのサイズは自動的に変わりません。画像ビューのコンテンツモードはAspect Fit
。
これは、1つのディメンションのみが制約されている場合に自動レイアウトで機能するAspectFit
UIImageView
から派生した実装です。もう1つは自動的に設定されます。画像のアスペクト比を維持し、画像の周囲に余白を追加しません。
@ algal idea のObjective-C実装を調整しましたが、次の違いがあります。
150
_と評価される式priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0
は、デフォルトの画像コンテンツサイズの制約の_1000
_の優先度に勝るには不十分です。そのため、アスペクト制約の優先度も_1000
_に引き上げられました。image
setterは複数回呼び出される可能性があるため、ここで余分なレイアウト計算を行わないことをお勧めします。required public init?(coder aDecoder: NSCoder)
とpublic override init(frame:CGRect)
をオーバーライドする必要があるのかわからないため、これら2つは削除されます。それらはUIImageView
によってオーバーライドされません。そのため、画像が設定されるまでアスペクト制約を追加する意味がありません。_AspectKeepUIImageView.h
_:
_NS_ASSUME_NONNULL_BEGIN
@interface AspectKeepUIImageView : UIImageView
- (instancetype)initWithImage:(nullable UIImage *)image;
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage;
@end
NS_ASSUME_NONNULL_END
_
_AspectKeepUIImageView.m
_:
_#import "AspectKeepUIImageView.h"
@implementation AspectKeepUIImageView
{
NSLayoutConstraint *_aspectContraint;
}
- (instancetype)initWithImage:(nullable UIImage *)image
{
self = [super initWithImage:image];
[self initInternal];
return self;
}
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage
{
self = [super initWithImage:image highlightedImage:highlightedImage];
[self initInternal];
return self;
}
- (void)initInternal
{
self.contentMode = UIViewContentModeScaleAspectFit;
[self updateAspectConstraint];
}
- (void)setImage:(UIImage *)image
{
[super setImage:image];
[self updateAspectConstraint];
}
- (void)updateAspectConstraint
{
CGSize imageSize = self.image.size;
CGFloat aspectRatio = imageSize.height > 0.0f
? imageSize.width / imageSize.height
: 0.0f;
if (_aspectContraint.multiplier != aspectRatio)
{
[self removeConstraint:_aspectContraint];
_aspectContraint =
[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeHeight
multiplier:aspectRatio
constant:0.f];
_aspectContraint.priority = UILayoutPriorityRequired;
[self addConstraint:_aspectContraint];
}
}
@end
_