无法在自定义 UIImageView 中强制图像四舍五入

问题描述 投票:0回答:1

我有一个自定义的 UIImageView,它的作用就像一个“轮播”,用户可以滑动它来查看图像(顺便说一下,我改编自 Medium 上的这篇优秀帖子

我希望将角圆化为 20,但我找不到 imageView 内容模式的正确值。

这是scaleAspectFit

这是scaleAspectFill

这是scaleToFill

我想要发生的是缩放图像以填充视图并保留其外观,我通常会使用 .scaleAspectFill 来实现。但由于这个自定义视图的设置方式,它变成了这种奇怪的混乱,正如你所看到的。

我已经粘贴了下面的自定义类 - 有人有什么想法吗?

class ImageCarouselView: UIView {
    private var images: [UIImage?] = []
    private var index = 0
    private let screenWidth = UIScreen.main.bounds.width

    var delegate: ImageCarouselViewDelegate?

    lazy var previousImageView = imageView(image: nil, contentMode: .scaleAspectFit)
    lazy var currentImageView = imageView(image: nil, contentMode: .scaleAspectFit)
    lazy var nextImageView = imageView(image: nil, contentMode: .scaleAspectFit)
    
    var topView = UIView()

    lazy var previousImageLeadingConstraint: NSLayoutConstraint = {
        return previousImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -screenWidth)
    }()

    lazy var currentImageLeadingConstraint: NSLayoutConstraint = {
        return currentImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0)
    }()

    lazy var nextImageLeadingConstraint: NSLayoutConstraint = {
        return nextImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: screenWidth)
    }()

    convenience init(_ images: [UIImage?]) {
        self.init()
        
        self.images = images


        self.setUpActions()
    }
    
    init() {
        super.init(frame: .zero)
        self.translatesAutoresizingMaskIntoConstraints = false
        
        self.heightAnchor.constraint(greaterThanOrEqualToConstant: 300).isActive = true
        
        
        self.layer.cornerRadius = 20
        self.clipsToBounds = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func load(images: [UIImage?]) {
        print("ImageCarouselView - Laod Images")
        self.images = images
        self.setUpActions()
    }
    
    private func setUpActions() {
        setupLayout()
        setupSwipeRecognizer()
        setupImages()
    }

    private func setupLayout() {
        self.subviews.forEach({ $0.removeFromSuperview() })

        addSubview(previousImageView)
        addSubview(currentImageView)
        addSubview(nextImageView)

        previousImageLeadingConstraint = previousImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -screenWidth)
        currentImageLeadingConstraint = currentImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0)
        nextImageLeadingConstraint = nextImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: screenWidth)

        NSLayoutConstraint.activate([
            previousImageLeadingConstraint,
            previousImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            previousImageView.widthAnchor.constraint(equalToConstant: screenWidth),

            currentImageLeadingConstraint,
            currentImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            currentImageView.widthAnchor.constraint(equalToConstant: screenWidth),

            nextImageLeadingConstraint,
            nextImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            nextImageView.widthAnchor.constraint(equalToConstant: screenWidth),
        ])
    }

    private func setupImages() {
        print(images.count)
        guard images.count > 0 else { return }
        
        currentImageView.image = images[self.index]

        guard images.count > 1 else { return }

        if (index == 0) {
            previousImageView.image = images[images.count - 1]
            nextImageView.image = images[index + 1]
        }

        if (index == (images.count - 1)) {
            previousImageView.image = images[index - 1]
            nextImageView.image = images[0]
        }
    }

    private func setupSwipeRecognizer() {
        guard images.count > 1 else { return }

        let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes))
        let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes))

        leftSwipe.direction = .left
        rightSwipe.direction = .right

        self.addGestureRecognizer(leftSwipe)
        self.addGestureRecognizer(rightSwipe)
    }

    @objc private func handleSwipes(_ sender: UISwipeGestureRecognizer) {
        if (sender.direction == .left) {
            showNextImage()
        }

        if (sender.direction == .right) {
            showPreviousImage()
        }
    }

    private func showPreviousImage() {
        previousImageLeadingConstraint.constant = 0
        currentImageLeadingConstraint.constant = screenWidth

        UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
            self.layoutIfNeeded()
        }, completion: { _ in
            self.nextImageView = self.currentImageView
            self.currentImageView = self.previousImageView
            self.previousImageView = self.imageView(image: nil, contentMode: .scaleAspectFit)

            self.index = self.index == 0 ? self.images.count - 1 : self.index - 1
            self.delegate?.imageCarouselView(self, didShowImageAt: self.index)
            self.previousImageView.image = self.index == 0 ? self.images[self.images.count - 1] : self.images[self.index - 1]

            self.setupLayout()
        })
    }

    private func showNextImage() {
        nextImageLeadingConstraint.constant = 0
        currentImageLeadingConstraint.constant = -screenWidth

        UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
            self.layoutIfNeeded()
        }, completion: { _ in
            self.previousImageView = self.currentImageView
            self.currentImageView = self.nextImageView
            self.nextImageView = self.imageView(image: nil, contentMode: .scaleAspectFit)

            self.index = self.index == (self.images.count - 1) ? 0 : self.index + 1
            self.delegate?.imageCarouselView(self, didShowImageAt: self.index)
            self.nextImageView.image = self.index == (self.images.count - 1) ? self.images[0] : self.images[self.index + 1]

            self.setupLayout()
        })
    }

    func imageView(image: UIImage? = nil, contentMode: UIImageView.ContentMode) -> UIImageView {
        let view = UIImageView()
        
        view.image = image
        view.contentMode = .scaleAspectFit
        view.translatesAutoresizingMaskIntoConstraints = false

        view.backgroundColor = UIColor.init(white: 0.3, alpha: 1)

        return view
    }
}
swift uikit uiimageview uiimage
1个回答
0
投票

创建“轮播”视图的方法有很多……这是一个有趣的方法。它不允许“左右拖动”——只能滑动——但如果这是期望的目标,那就没问题了。

有几件事它做错了......

第一:

private let screenWidth = UIScreen.main.bounds.width

是一个非常糟糕的主意。除非视图实际上是屏幕的整个宽度,否则该类将无法工作。它也不会适应框架变化(例如设备旋转)。而且,例如,如果应用程序在 iPad 上以多任务模式运行,它将会严重失败。

所以,让我们使用 view 宽度来代替,并在

layoutSubviews()
中更新它:

private var myWidth: CGFloat = 0.0

override func layoutSubviews() {
    super.layoutSubviews()
    
    // if the width has changed...
    //  this will be true on first layout
    //  and on frame change (such as device rotation)
    if myWidth != bounds.width {
        myWidth = bounds.width
        // update image view positions
        previousImageLeadingConstraint.constant = -myWidth
        currentImageLeadingConstraint.constant = 0
        nextImageLeadingConstraint.constant = myWidth
    }
}

接下来,代码创建一个新的图像视图,并在每次滑动时完全重建视图层次结构...这比所需的处理量要多得多。

相反,我们可以重新定位现有图像视图并在滑动动画完成时更新其

.image
属性。

所以,如果我们假设我们以此开始(红色虚线是“幻灯片放映视图”的框架):

问题是——什么应该有圆角?

在动画制作过程中考虑这些图像:

您希望在动画过程中角点的外观将决定我们是否对图像视图、视图本身、两者都进行圆角处理,或者都不进行圆角处理。


这是您的

ImageCarouselView
类的修改版本:

class ImageCarouselView: UIView {
    
    // public properties
    public var cornerRadius: CGFloat = 32.0 { didSet { updateCorners() } }
    public var animDuration: Double = 0.3
    public var shouldRoundFrame: Bool = true { didSet { updateCorners() } }
    public var shouldRoundImages: Bool = false { didSet { updateCorners() } }
    
    public var delegate: ImageCarouselViewDelegate?
    
    // private properties
    private var images: [UIImage?] = []
    private var index = 0
    private var myWidth: CGFloat = 0.0
    
    private lazy var previousImageView = imageView(image: nil, contentMode: .scaleAspectFill)
    private lazy var currentImageView = imageView(image: nil, contentMode: .scaleAspectFill)
    private lazy var nextImageView = imageView(image: nil, contentMode: .scaleAspectFill)

    private var previousImageLeadingConstraint: NSLayoutConstraint!
    private var currentImageLeadingConstraint: NSLayoutConstraint!
    private var nextImageLeadingConstraint: NSLayoutConstraint!

    convenience init(_ images: [UIImage?]) {
        self.init()
        self.images = images
        self.setUpActions()
    }
    
    init() {
        super.init(frame: .zero)
        self.translatesAutoresizingMaskIntoConstraints = false
        self.heightAnchor.constraint(greaterThanOrEqualToConstant: 300).isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func load(images: [UIImage?]) {
        print("ImageCarouselView - Laod Images")
        self.images = images
        self.setUpActions()
    }
    
    private func setUpActions() {
        self.clipsToBounds = true
        setupLayout()
        setupSwipeRecognizer()
        setupImages()
        updateCorners()
    }
    
    private func setupLayout() {
        // this should only get called once, on init
        //  so we shouldn't have any subviews
        //  but in case it gets called again...
        self.subviews.forEach({ $0.removeFromSuperview() })
        
        addSubview(previousImageView)
        addSubview(currentImageView)
        addSubview(nextImageView)
        
        previousImageLeadingConstraint = previousImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -myWidth)
        currentImageLeadingConstraint = currentImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0)
        nextImageLeadingConstraint = nextImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: myWidth)
        
        NSLayoutConstraint.activate([
            previousImageLeadingConstraint,
            currentImageLeadingConstraint,
            nextImageLeadingConstraint,
            
            // all image views centered vertically
            previousImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            currentImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            nextImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            
            // all image views have same width as self
            previousImageView.widthAnchor.constraint(equalTo: self.widthAnchor),
            currentImageView.widthAnchor.constraint(equalTo: self.widthAnchor),
            nextImageView.widthAnchor.constraint(equalTo: self.widthAnchor),
            
            // all image views have same height as self
            previousImageView.heightAnchor.constraint(equalTo: self.heightAnchor),
            currentImageView.heightAnchor.constraint(equalTo: self.heightAnchor),
            nextImageView.heightAnchor.constraint(equalTo: self.heightAnchor),
        ])
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // if the width has changed...
        //  this will be true on first layout
        //  and on frame change (such as device rotation)
        if myWidth != bounds.width {
            myWidth = bounds.width
            // update image view positions
            previousImageLeadingConstraint.constant = -myWidth
            currentImageLeadingConstraint.constant = 0
            nextImageLeadingConstraint.constant = myWidth
        }
    }
    
    private func setupImages() {
        
        guard images.count > 0 else { return }
        
        currentImageView.image = images[self.index]
        
        guard images.count > 1 else { return }
        
        if (index == 0) {
            previousImageView.image = images[images.count - 1]
            nextImageView.image = images[index + 1]
        }
        
        if (index == (images.count - 1)) {
            previousImageView.image = images[index - 1]
            nextImageView.image = images[0]
        }
    }
    
    private func setupSwipeRecognizer() {
        guard images.count > 1 else { return }
        
        let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes))
        let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes))
        
        leftSwipe.direction = .left
        rightSwipe.direction = .right
        
        self.addGestureRecognizer(leftSwipe)
        self.addGestureRecognizer(rightSwipe)
    }
    
    @objc private func handleSwipes(_ sender: UISwipeGestureRecognizer) {
        if (sender.direction == .left) {
            showNextImage()
        }
        
        if (sender.direction == .right) {
            showPreviousImage()
        }
    }
    
    private func showPreviousImage() {
        // we're sliding previousImageView - currently "out-of-view on-the-left"
        //  from left-to-right to x: 0
        previousImageLeadingConstraint.constant = 0
        
        // we're sliding currentImageView - currently visible
        //  from left-to-right to x: width of self
        currentImageLeadingConstraint.constant = myWidth
        
        UIView.animate(withDuration: animDuration, delay: 0.0, options: .curveEaseIn, animations: {
            self.layoutIfNeeded()
        }, completion: { _ in
            
            // move previousImageView back to "out-of-view on-the-left"
            self.previousImageLeadingConstraint.constant = -self.myWidth
            
            // move currentImageView back to x: 0
            self.currentImageLeadingConstraint.constant = 0.0
            
            // set nextImageView's image to current image
            self.nextImageView.image = self.currentImageView.image
            // set currentImageView's image to previous image
            self.currentImageView.image = self.previousImageView.image
            
            // update previousImageView's image based on indexing
            self.index = self.index == 0 ? self.images.count - 1 : self.index - 1
            self.delegate?.imageCarouselView(self, didShowImageAt: self.index)
            self.previousImageView.image = self.index == 0 ? self.images[self.images.count - 1] : self.images[self.index - 1]
            
        })
    }
    
    private func showNextImage() {
        // we're sliding nextImageView - currently "out-of-view on-the-right"
        //  from right-to-left to x: 0
        nextImageLeadingConstraint.constant = 0

        // we're sliding currentImageView - currently visible
        //  from right-to-left to x: -width of self
        currentImageLeadingConstraint.constant = -myWidth
        
        UIView.animate(withDuration: animDuration, delay: 0.0, options: .curveEaseIn, animations: {
            self.layoutIfNeeded()
        }, completion: { _ in
            
            // move nextImageView back to "out-of-view on-the-right"
            self.nextImageLeadingConstraint.constant = self.myWidth
            
            // move currentImageView back to x: 0
            self.currentImageLeadingConstraint.constant = 0.0
            
            // set previousImageView's image to current image
            self.previousImageView.image = self.currentImageView.image
            // set currentImageView's image to next image
            self.currentImageView.image = self.nextImageView.image
            
            // update nextImageView's image based on indexing
            self.index = self.index == (self.images.count - 1) ? 0 : self.index + 1
            self.delegate?.imageCarouselView(self, didShowImageAt: self.index)
            self.nextImageView.image = self.index == (self.images.count - 1) ? self.images[0] : self.images[self.index + 1]
            
        })
    }
    
    func imageView(image: UIImage? = nil, contentMode: UIImageView.ContentMode) -> UIImageView {
        let view = UIImageView()
        
        view.clipsToBounds = true
        view.image = image
        view.contentMode = .scaleAspectFill
        view.translatesAutoresizingMaskIntoConstraints = false
        
        view.backgroundColor = UIColor.init(white: 0.3, alpha: 1)
        
        return view
    }
    
    private func updateCorners() {
        // round the corners of self and the image views as specified
        var r: CGFloat
        
        r = self.shouldRoundFrame ? self.cornerRadius : 0.0
        self.layer.cornerRadius = r
        
        r = self.shouldRoundImages ? self.cornerRadius : 0.0
        [previousImageView, currentImageView, nextImageView].forEach { v in
            v.layer.cornerRadius = r
        }
    }
}


protocol ImageCarouselViewDelegate: NSObjectProtocol {
    func imageCarouselView(_ imageCarouselView: ImageCarouselView, didShowImageAt index: Int)
}

和一个示例视图控制器:

class SlideShowViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var images: [UIImage] = []
        
        ["ss01", "ss02", "ss03","ss04",].forEach { sName in
            guard let img = UIImage(named: sName) else {
                fatalError("Could not load image: \(sName)")
            }
            images.append(img)
        }
        
        let slideshowView = ImageCarouselView(images)
        slideshowView.delegate = self
        
        slideshowView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(slideshowView)
        
        let g = view.safeAreaLayoutGuide
        
        // let's make the slideshowView frame
        //  90% of the view width, with max of 600-points
        //  300-points height
        //  centered horizontally and vertically
        
        let maxWidth: CGFloat = 600.0
        let targetW: NSLayoutConstraint = slideshowView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9)
        targetW.priority = .required - 1
        
        NSLayoutConstraint.activate([
            targetW,
            slideshowView.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth),
            slideshowView.heightAnchor.constraint(equalToConstant: 300.0),
            slideshowView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            slideshowView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])
        
        // change these to see the different "corner rounding"
        slideshowView.shouldRoundFrame = true
        slideshowView.shouldRoundImages = true
        
        // if you want to adjust the animation speed
        //slideshowView.animDuration = 1.0
        
    }
    
}
extension SlideShowViewController: ImageCarouselViewDelegate {
    func imageCarouselView(_ imageCarouselView: ImageCarouselView, didShowImageAt index: Int) {
        // do something with index
        print("didShow:", index)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.