ios - 具有动态大小和纵横比的 UIImages。我究竟做错了什么?

标签 ios swift autolayout

所以我正在尝试做类似 instagram 的 View ,这应该相当简单。仅启动一个 UITableViewController 和一个仅包含标签和图像的 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
    }

}

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

但是,一旦我加载应用程序,边距就非常大:enter image description here

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

我该如何解决这个问题?

最佳答案

您似乎让 ImageView 的大小由 ImageView 的固有大小决定。但是 ImageView 的固有大小由图像的大小决定,与 ImageView 的内容模式无关。

您可以为 ImageView 的大小定义一个约束,而不是依赖于 ImageView 的固有大小,例如:

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 返回时对约束进行即时调整,单元格将生成关于表格 View 单元格固有高度的各种自动布局警告。 (即使约束实际上是完全可满足的)。

  • 其次,恕我直言,当您的单元格的高度可以根据外部输入(例如图像的大小)发生变化时,您通常希望限制单元格中 ImageView 的高度。如果让单元格中的图像增长到荒谬的高度(如果图像是 200 像素宽和 2000 像素高的胡萝卜的垂直照片怎么办),您最终可能会得到奇怪的用户体验,其中 ImageView 太高以至于您丢失整个“我在表格 View 中滚动”的氛围。

    所以我喜欢限制 ImageView 的最大高度,而不考虑图像。因此,在 IB 中,我定义了一个约束条件,表示 ImageView 的高度应为 <=。到 200 点(使用您想要的任何值)。然后,我为“缩放方面适合”的图像使用内容模式。


请注意,除上述方法外,另一种方法是保持约束不变,但调整图像本身的大小,以便如果它真的很大,则将其缩小到适合图像宽度的大小看法。例如,您可以执行以下操作:

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)
    }
    
}

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

关于ios - 具有动态大小和纵横比的 UIImages。我究竟做错了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44169594/

相关文章:

objective-c - iOS:如何获取嵌套关系的所有对象parent.child.theObjectsIWant

ios - 在 AWS 中获取请求时出错

ios - Apple 表盘 : change from an app running on watch

iOS 对自定义键盘设置了必需的高度限制

ios - Xamarin iOS 导航栏图像

ios - 在静态函数中传递 Self

ios - 异步请求如果失败了怎么办?

ios - BSP Dungeon Generator 出现无法修复的错误

ios - 以编程方式自动布局不垂直居中

ios - 将应用程序升级到 iOS 7 - 错误 "Auto Layout on iOS Versions prior to 6.0"(但不希望 iOS 7 更旧)