Macos NSImageView图像方面填充?

Macos NSImageView图像方面填充?,macos,nsimageview,Macos,Nsimageview,因此,我习惯于UIImageView,能够设置图像在其中显示的不同方式。例如AspectFill模式等 我想在mac应用程序上使用NSImageView完成同样的事情。NSImageView在这方面是否与UIImageView类似,或者如何在NSImageView中显示图像并选择不同的显示方式?我也有同样的问题。我想让图像缩放以填充,但保持原始图像的纵横比。奇怪的是,这并不像看上去的那么简单,并且没有在NSImageView中开箱即用。我希望NSImageView在使用superview调整大小

因此,我习惯于
UIImageView
,能够设置图像在其中显示的不同方式。例如
AspectFill
模式等


我想在mac应用程序上使用
NSImageView
完成同样的事情。
NSImageView
在这方面是否与
UIImageView
类似,或者如何在
NSImageView
中显示图像并选择不同的显示方式?

我也有同样的问题。我想让图像缩放以填充,但保持原始图像的纵横比。奇怪的是,这并不像看上去的那么简单,并且没有在NSImageView中开箱即用。我希望NSImageView在使用superview调整大小时能够很好地缩放。我在github上找到了NSImageView子类:

可以使用下面的
NSImageView
功能更新图像缩放

[imageView setImageScaling:NSScaleProportionally];
以下是更改图像显示属性的更多选项

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

您可以使用此选项:图像将被强制填充视图大小

(方面填充)


(方面匹配)


(中上)


它对我有用

您可能会发现,将
NSView
子类化并提供一个
CALayer
来为您填充方面要容易得多。下面是这个
NSView
子类的
init
可能的样子

- (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,您将得到一个默认的背景图层)


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

以下是我使用的,用Swift编写的。这种方法适用于故事板-只需使用普通的NSImageView,然后将类框中的名称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
        }
    }

}

我一直在努力弄清楚如何制作一个方面填充剪辑到边界

图片来源:

最后,我创建了自己的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 development的新手,我来自iOS开发,这就是为什么我很惊讶我找不到clipToBound属性,也许它存在,但我找不到它

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


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

使用CALayer显示图像意味着它不会响应暗模式更改。 我无法更改CALayer的内容,因此我使用手动调整大小的NSView构建了一个新版本

注意-尽管您可以添加此视图并在InterfaceBuilder中设置图像,但它不会显示在那里。我无法让@IBDesignable工作

import Foundation
import AppKit

open class AspectFillNSImageView : NSView {

    @IBInspectable
    open var image: NSImage? {
        didSet {
            subImageView.image = image
        }
    }

    open override func prepareForInterfaceBuilder() {
        self.needsLayout = true
    }


    
    open override func layout() {
        super.layout()

        guard let subImage = subImageView.image else {
            subImageView.frame = bounds
            return
        }

        let xScale = subImage.size.width / self.bounds.size.width
        let yScale = subImage.size.width / self.bounds.size.height

        let aspectFitScale = min(xScale,yScale)
        let newSize = CGSize.init(x: subImage.size.width / aspectFitScale,
                                  y: subImage.size.height / aspectFitScale)
        let offset = CGPoint.init(x: (bounds.width - newSize.width)/2,
                                  y: (bounds.height - newSize.height)/2)

        subImageView.frame = CGRect.init(origin: offset, size: newSize)
    }
    
    var subImageView = NSImageView.init(frame: .zero)
    func commonInit() {
        self.translatesAutoresizingMaskIntoConstraints = false
        subImageView.imageScaling = .scaleProportionallyUpOrDown
        self.addSubview(subImageView)

        
        //observe frame changes
        self.postsFrameChangedNotifications = true
        NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification,
                                               object: self, queue: nil) { [weak self] (_) in
                                                self?.needsLayout = true
        }
        
        if let anImage = self.image {
            self.image = anImage
            //doLayout()
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    public override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        
        commonInit()
    }


    required public init?(coder: NSCoder) {
        super.init(coder: coder)

        commonInit()
    }
}

您知道为什么会发生这种情况吗?AspectFit与AspectFill不同。nsImageScaleaxes独立拉伸图像以填充NSImageView的整个帧。谢谢从文档中可以看出:此设置不保留图像的纵横比。对我有用。谢谢这里有一个问题-因为它使用CALayer来显示图像。它不会改变内容以响应暗模式的改变。有关解决该问题的不同方法,请参见我的答案。根据doc,它是“尽管您可以将NSImage对象直接指定给CALayer对象的Content属性,但这样做可能并不总是产生最佳结果。您可以使用LayerContent(ForContentsCale:)代替使用图像对象方法获取可用于图层内容的对象。“在不同的操作系统版本上,这种行为是否可能不同?”?我在10.13.6上试过这个,但它不起作用,而一个运行mojave的大学显示它工作良好。它不会将图像剪裁到图像视图的边界上。没错,这是一个方面填充,而不是方面匹配。。。彼得这太棒了,谢谢你,彼得!我不使用open,只在子类中添加override var image[…],谢谢!如何实现我的目标并不明显:)
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
        }
    }

}
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)
    }
}
import Foundation
import AppKit

open class AspectFillNSImageView : NSView {

    @IBInspectable
    open var image: NSImage? {
        didSet {
            subImageView.image = image
        }
    }

    open override func prepareForInterfaceBuilder() {
        self.needsLayout = true
    }


    
    open override func layout() {
        super.layout()

        guard let subImage = subImageView.image else {
            subImageView.frame = bounds
            return
        }

        let xScale = subImage.size.width / self.bounds.size.width
        let yScale = subImage.size.width / self.bounds.size.height

        let aspectFitScale = min(xScale,yScale)
        let newSize = CGSize.init(x: subImage.size.width / aspectFitScale,
                                  y: subImage.size.height / aspectFitScale)
        let offset = CGPoint.init(x: (bounds.width - newSize.width)/2,
                                  y: (bounds.height - newSize.height)/2)

        subImageView.frame = CGRect.init(origin: offset, size: newSize)
    }
    
    var subImageView = NSImageView.init(frame: .zero)
    func commonInit() {
        self.translatesAutoresizingMaskIntoConstraints = false
        subImageView.imageScaling = .scaleProportionallyUpOrDown
        self.addSubview(subImageView)

        
        //observe frame changes
        self.postsFrameChangedNotifications = true
        NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification,
                                               object: self, queue: nil) { [weak self] (_) in
                                                self?.needsLayout = true
        }
        
        if let anImage = self.image {
            self.image = anImage
            //doLayout()
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    public override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        
        commonInit()
    }


    required public init?(coder: NSCoder) {
        super.init(coder: coder)

        commonInit()
    }
}