具有动态尺寸和宽高比的UIImages。我究竟做错了什么?

时间:2017-05-24 22:39:18

标签: ios swift autolayout

所以我试图做类似于Instagram的视图,这应该相当简单。仅开始UITableViewCell,以及仅包含标签和图片的class FoodListVC: UITableViewController { let samples = [ "things", "stuff", "foo" ] let images = [ UIImage(named: "photo1"), UIImage(named: "photo2"), UIImage(named: "photo3") ] override func viewDidLoad() { super.viewDidLoad() // Row size tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 88 } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return samples.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "foodCell", for: indexPath) as! FoodCell cell.setContent(title: samples[indexPath.row], image: images[indexPath.row]!) return cell } }

class FoodCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var mainImage: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    func setContent(title: String, image: UIImage) {
        titleLabel.text = title

        mainImage.image = image
        mainImage.backgroundColor = UIColor.red
    }

}

{{1}}

在界面布局中,事情看起来也很简单: enter image description here

但是,一旦我加载了应用,边距就会很大:enter image description here

我的理论是,图像比手机中可用的尺寸大,并且具有“适合度”,它只是在图像的上方和下方增加了一些透明度。

我该如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

您似乎是让图像视图的大小由图像视图的内在大小决定。但无论图像视图的内容模式如何,图像视图的固有大小都由图像大小决定。

您可以为图像视图的大小定义约束,而不是依赖于图像视图的固有大小,例如:

class FoodCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var mainImage: UIImageView!

    private var aspectConstraint: NSLayoutConstraint?

    func setContent(title: String, image: UIImage) {

        // remove constraint, if any

        if aspectConstraint != nil {
            mainImage.removeConstraint(aspectConstraint!)
        }

        // add constraint

        let ratio = image.size.width / image.size.height
        aspectConstraint = NSLayoutConstraint(item: mainImage, attribute: .width, relatedBy: .equal, toItem: mainImage, attribute: .height, multiplier: ratio, constant: 0)
        aspectConstraint?.priority = 999
        mainImage.addConstraint(aspectConstraint!)

        // set the image and label

        titleLabel.text = title
        mainImage.image = image
        mainImage.backgroundColor = .red
    }

}

请注意,我将此设置为高优先级约束,但小于1000.我这样做有两个原因:

  • 首先,如果您从cellForRowAt返回时执行即时调整约束,则单元格将生成有关表格单元格内在高度的各种自动布局警告(均匀如果约束实际上是完全可满足的。)

  • 其次,如果您的单元格的高度可以根据外部输入(例如图像的大小)而改变,那么您经常需要约束单元格中图像视图的高度,恕我直言。如果你让细胞中的图像变得荒谬的高度(如果图像是一张200像素宽和2000像素高的胡萝卜的垂直照片,那么你最终可能会遇到奇怪的用户体验,其中图像视图太高而你输了整个"我在桌面视图中滚动"的氛围。

    所以我喜欢约束图像视图的最大高度,无论图像如何。所以,在IB中,我定义了一个约束,它表示图像视图的高度应该是<=到200点(使用你想要的任何值)。然后我使用内容模式来显示&#34;缩放方面适合&#34;。

请注意,除了上述内容之外,另一种方法是保留您的约束,但是调整图像本身的大小,这样如果它非常大,那么它会缩小到适合于图像视图的宽度。例如,您可以执行以下操作:

func setContent(title: String, image: UIImage) {
    titleLabel.text = title

    if image.size.width > mainImage.frame.width {
        let size = CGSize(width: mainImage.frame.width, height: max(200, mainImage.frame.width * image.size.height / image.size.width))
        mainImage.image = image.scaledAspectFit(to: size)
    } else {
        mainImage.image = image
    }

    mainImage.backgroundColor = .red
}

其中:

extension UIImage {

    /// Resize the image to be the required size, stretching it as needed.
    ///
    /// - parameter newSize:      The new size of the image.
    /// - parameter contentMode:  The `UIViewContentMode` to be applied when resizing image.
    ///                           Either `.scaleToFill`, `.scaleAspectFill`, or `.scaleAspectFit`.
    ///
    /// - returns:                Return `UIImage` of resized image.

    func scaled(to newSize: CGSize, contentMode: UIViewContentMode = .scaleToFill) -> UIImage? {
        if contentMode == .scaleToFill {
            return filled(to: newSize)
        } else if contentMode == .scaleAspectFill || contentMode == .scaleAspectFit {
            let horizontalRatio = size.width  / newSize.width
            let verticalRatio   = size.height / newSize.height

            let ratio: CGFloat!
            if contentMode == .scaleAspectFill {
                ratio = min(horizontalRatio, verticalRatio)
            } else {
                ratio = max(horizontalRatio, verticalRatio)
            }

            let sizeForAspectScale = CGSize(width: size.width / ratio, height: size.height / ratio)
            let image = filled(to: sizeForAspectScale)
            if contentMode == .scaleAspectFill {
                let subRect = CGRect(
                    x: floor((sizeForAspectScale.width - newSize.width) / 2.0),
                    y: floor((sizeForAspectScale.height - newSize.height) / 2.0),
                    width: newSize.width,
                    height: newSize.height)
                return image?.cropped(to: subRect)
            }
            return image
        }
        return nil
    }

    /// Resize the image to be the required size, stretching it as needed.
    ///
    /// - parameter newSize:   The new size of the image.
    ///
    /// - returns:             Resized `UIImage` of resized image.

    func filled(to newSize: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(newSize, false, scale)
        draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }

    /// Crop the image to be the required size.
    ///
    /// - parameter bounds:    The bounds to which the new image should be cropped.
    ///
    /// - returns:             Cropped `UIImage`.

    func cropped(to bounds: CGRect) -> UIImage? {
        var rect = bounds
        rect.size.width *= scale
        rect.size.height *= scale

        if let imageRef = cgImage?.cropping(to: rect) {
            return UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation)
        } else {
            return nil
        }
    }

    /// Resize the image to fill the rectange of the specified size, preserving the aspect ratio, trimming if needed.
    ///
    /// - parameter newSize:   The new size of the image.
    ///
    /// - returns:             Return `UIImage` of resized image.

    func scaledAspectFill(to newSize: CGSize) -> UIImage? {
        return scaled(to: newSize, contentMode: .scaleAspectFill);
    }

    /// Resize the image to fit within the required size, preserving the aspect ratio, with no trimming taking place.
    ///
    /// - parameter newSize:   The new size of the image.
    ///
    /// - returns:             Return `UIImage` of resized image.

    func scaledAspectFit(to newSize: CGSize) -> UIImage? {
        return scaled(to: newSize, contentMode: .scaleAspectFit)
    }

}

这&#34;调整图像大小&#34;方法有另一个优点。小图像视图中的大图像仍然需要大量内存。但是,如果您将图像调整为适合图像视图的大小,则可以避免浪费内存。