【问题标题】:NSImageView image aspect fill?NSImageView 图像方面填充?
【发布时间】:2021-09-08 19:24:49
【问题描述】:

所以我习惯了UIImageView,并且能够设置其图像在其中显示的不同方式。比如AspectFill模式等...

我想在 Mac 应用程序上使用 NSImageView 来完成同样的事情。 NSImageView 在这方面与UIImageView 的工作方式相似吗?或者我将如何在NSImageView 中显示图像并选择不同的显示方式?

【问题讨论】:

    标签: macos nsimageview


    【解决方案1】:

    您可能会发现继承NSView 并提供一个为您填充方面的CALayer 会容易得多。下面是 init 对于这个 NSView 子类的样子。

    - (id)initWithFrame:(NSRect)frame andImage:(NSImage*)image
    {
      self = [super initWithFrame:frame];
      if (self) {
        self.layer = [[CALayer alloc] init];
        self.layer.contentsGravity = kCAGravityResizeAspectFill;
        self.layer.contents = image;
        self.wantsLayer = YES;
      }
      return self;
    }
    

    注意先设置层,再设置wantsLayer的顺序非常重要(如果你先设置wantsLayer,你会得到一个默认的backing layer)。

    您可以有一个简单地更新图层内容的setImage 方法。

    【讨论】:

    • 根据 doc developer.apple.com/documentation/appkit/nsimage 它是“虽然您可以将 NSImage 对象直接分配给 CALayer 对象的内容属性,但这样做可能并不总是产生最佳结果。而不是使用您的图像对象,您可以使用 layerContents(forContentsScale:) 方法获取可用于图层内容的对象。"
    • 这是否可能在不同的操作系统版本上表现不同?我在 10.13.6 上试过这个,但它没有工作,而一个运行 mojave 的大学显示它工作正常
    【解决方案2】:

    这是我正在使用的,用 Swift 编写的。这种方法适用于情节提要 - 只需使用普通的 NSImageView,然后将 Class 框中的名称 NSImageView 替换为 MyAspectFillImageNSImageView ...

    open class MyAspectFillImageNSImageView : NSImageView {
      
      open override var image: NSImage? {
        set {
          self.layer = CALayer()
          self.layer?.contentsGravity = kCAGravityResizeAspectFill
          self.layer?.contents = newValue
          self.wantsLayer = true
          
          super.image = newValue
        }
        
        get {
          return super.image
        }
      }
    
        public override init(frame frameRect: NSRect) {
            super.init(frame: frameRect)
        }
        
        //the image setter isn't called when loading from a storyboard
        //manually set the image if it is already set
        required public init?(coder: NSCoder) {
            super.init(coder: coder)
            
            if let theImage = image {
                self.image = theImage
            }
        }
    
    }
    

    【讨论】:

    • 这不会将图像裁剪到图像视图的边界
    • 没错 - 这是一个方面填充,而不是一个方面适合......皮特
    • 这太棒了,谢谢皮特!我不使用 open,只是在子类中添加 override var image [...]。
    • 谢谢!如何实现我想要的并不明显:)
    【解决方案3】:

    我遇到了同样的问题。我想让图像被缩放以填充但保持原始图像的纵横比。奇怪的是,这并不像看起来那么简单,并且不是开箱即用的 NSImageView。我希望 NSImageView 在使用超级视图调整大小时很好地缩放。我做了一个插入式 NSImageView 子类,你可以在 github 上找到:KPCScaleToFillNSImageView

    【讨论】:

    • 你知道为什么会这样吗?
    【解决方案4】:

    您可以使用这个:图像将被强制填充视图大小

    (方面填充)

    imageView.imageScaling = .scaleAxesIndependently
    


    (宽高比)

    imageView.imageScaling = .scaleProportionallyUpOrDown
    


    (中上)

    imageView.imageScaling = .scaleProportionallyDown
    

    它对我有用。

    【讨论】:

    • 来自文档:此设置不保留图像的纵横比。
    • 为我工作。谢谢!
    • 这里有一个问题 - 因为它使用 CALayer 来显示图像。它不会响应暗模式更改而更改内容。请参阅我的答案以了解解决该问题的不同方法。
    • imageView.imageScaling = .scaleAxesIndependently 这不是用于“Aspect Fill”,这只是用于“Scale to Fill”。
    【解决方案5】:

    我很难弄清楚如何制作Aspect Fill Clip to Bounds

    图片来源:https://osxentwicklerforum.de/index.php/Thread/28812-NSImageView-Scaling-Seitenverh%C3%A4ltnis/

    最后我创建了自己的 NSImageView 子类,希望对大家有所帮助:

    import Cocoa
    
    @IBDesignable
    class NSImageView_ScaleAspectFill: NSImageView {
    
        @IBInspectable
        var scaleAspectFill : Bool = false
    
    
        override func awakeFromNib() {
            // Scaling : .scaleNone mandatory
            if scaleAspectFill { self.imageScaling = .scaleNone }
        }
    
        override func draw(_ dirtyRect: NSRect) {
    
            if scaleAspectFill, let _ = self.image {
    
                // Compute new Size
                let imageViewRatio   = self.image!.size.height / self.image!.size.width
                let nestedImageRatio = self.bounds.size.height / self.bounds.size.width
                var newWidth         = self.image!.size.width
                var newHeight        = self.image!.size.height
    
    
                if imageViewRatio > nestedImageRatio {
    
                    newWidth = self.bounds.size.width
                    newHeight = self.bounds.size.width * imageViewRatio
                } else {
    
                    newWidth = self.bounds.size.height / imageViewRatio
                    newHeight = self.bounds.size.height
                }
    
                self.image!.size.width  = newWidth
                self.image!.size.height = newHeight
    
            }
    
            // Draw AFTER resizing
            super.draw(dirtyRect)
        }
    }
    

    加上这是@IBDesignable,所以你可以在故事板中设置它

    警告

    • 我是 MacOS Swift 开发的新手,我来自 iOS 开发,这就是为什么我很惊讶我找不到 clipToBound 属性,也许它存在但我找不到找到它!

    • 关于代码,我怀疑这会消耗很多,而且随着时间的推移会产生修改原始图像比例的副作用。这种副作用对我来说似乎可以忽略不计。

    如果他们的设置允许NSImageView 剪辑到边界,请删除此答案:]

    【讨论】:

    • 从 macOS 10.5 开始可以剪裁到边界:view.layer.masksToBounds = true
    【解决方案6】:

    图片缩放可以用NSImageView下面的函数更新。

    [imageView setImageScaling:NSScaleProportionally];
    

    这里有更多更改图像显示属性的选项。

    enum {
        NSScaleProportionally = 0, // Deprecated. Use NSImageScaleProportionallyDown
        NSScaleToFit,              // Deprecated. Use NSImageScaleAxesIndependently
        NSScaleNone                // Deprecated. Use NSImageScaleNone
    };
    

    【讨论】:

    • AspectFit 与 AspectFill 不同。
    • NSImageScaleAxes 独立拉伸图像以填满 NSImageView 的整个框架。谢谢!
    【解决方案7】:

    这是另一种在后台使用 SwiftUI 的方法

    这里的主要优点是,如果您的图像具有明暗模式,那么当系统外观发生变化时它们会受到尊重 (我无法让它与其他方法一起使用)

    这依赖于您的资产中存在的带有 imageName 的图像

    import Foundation
    import AppKit
    import SwiftUI
    
    
    open class AspectFillImageView : NSView {
    
        @IBInspectable
        open var imageName: String?
        {
            didSet {
                if imageName != oldValue {
                    insertSwiftUIImage(imageName)
                }
            }
        }
    
        open override func prepareForInterfaceBuilder() {
            self.needsLayout = true
        }
    
    
        func insertSwiftUIImage(_ name:String?){
            self.removeSubviews()
            
            guard let name = name else {
                return
            }
            
            let iv = Image(name).resizable().scaledToFill()
            
            let hostView = NSHostingView(rootView:iv)
            self.addSubview(hostView)
          
            //I'm using PureLayout to pin the subview. You will have to rewrite this in your own way...
            hostView.autoPinEdgesToSuperviewEdges()
        }
        
        
        func commonInit() {
            insertSwiftUIImage(imageName)
        }
    
        
        public override init(frame frameRect: NSRect) {
            super.init(frame: frameRect)
            
            commonInit()
        }
    
    
        required public init?(coder: NSCoder) {
            super.init(coder: coder)
    
            commonInit()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-03
      • 1970-01-01
      • 2016-08-20
      • 2018-07-28
      • 2015-11-23
      • 1970-01-01
      • 1970-01-01
      • 2018-05-02
      相关资源
      最近更新 更多