如何创建一个 class 来显示来自数组的一行中的多个 UIImageView?
How to create a class that shows multiple UIImageViews in a line from an Array?
我 运行 在尝试在集合视图单元格中创建可视元素时遇到了一个奇怪的错误。 class 获取一组 UserModel 并将所有个人资料图片排成一行。如果数组中有超过 4 个用户,最后一个图像视图是带有“+N”的模糊图像,让用户知道还有更多用户无法显示在屏幕上。
实际行为:
预期行为:
当您转到另一个屏幕并返回时,图像会自行更正。
这是我为获得以下结果而创建的 class。
import Foundation
import UIKit
class UserStack: UIView{
var userArray: [UserModel]!
var size: CGFloat = 50
init(userArray: [UserModel]){
super.init(frame: .zero)
self.userArray = userArray
if self.userArray.count <= 4{
fourOrLess()
}else{
fiveOrMore()
}
}
func fourOrLess(){
var spacing: CGFloat = 0
userArray.forEach { user in
let imageView = UIImageView()
guard let url = user.imageURL else {return}
imageView.sd_setImage(with: URL(string: url)) { i, e, c, u in
if let e = e{
print("DEBUG: Error setting userStack - \(e)")
return
}
self.addSubview(imageView)
self.bringSubviewToFront(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = self.size * 0.4
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 0.75
imageView.layer.borderColor = UIColor.white.cgColor
///LAYOUT
imageView.setDimensions(height: self.size, width: self.size)
imageView.centerY(inView: self)
imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
//DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
spacing += (self.size - 10)
// }
}
}
}
func fiveOrMore(){
var count: Int = 1
var spacing: CGFloat = 0
userArray.forEach { user in
if count < 4{
count += 1
let imageView = UIImageView()
guard let url = user.imageURL else {return}
imageView.sd_setImage(with: URL(string: url)) { i, e, c, u in
if let e = e{
print("DEBUG: Error setting userStack - \(e)")
return
}
self.addSubview(imageView)
self.bringSubviewToFront(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = self.size * 0.4
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 0.75
imageView.layer.borderColor = UIColor.white.cgColor
///LAYOUT
imageView.setDimensions(height: self.size, width: self.size)
imageView.centerY(inView: self)
imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
spacing += (self.size - 10)
}
}else{
///NUMBER VIEW
let numberImageView = UIImageView()
let blurView = UIView()
let numberLabel = UILabel()
guard let _URL = userArray.last?.imageURL else {return}
numberImageView.sd_setImage(with: URL(string: _URL)) { i, e, c, u in
if let e = e{
print("DEBUG: Error setting userStack - \(e)")
return
}
self.addSubview(numberImageView)
numberImageView.translatesAutoresizingMaskIntoConstraints = false
numberImageView.contentMode = .scaleAspectFill
numberImageView.layer.cornerRadius = self.size * 0.4
numberImageView.layer.masksToBounds = true
//numberImageView.layer.borderWidth = 0.75
//numberImageView.layer.borderColor = UIColor.white.cgColor
numberImageView.addSubview(blurView)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.backgroundColor = .init(white: 0.2, alpha: 0.9)
blurView.layer.cornerRadius = self.size * 0.4
blurView.layer.masksToBounds = true
blurView.layer.borderWidth = 0.75
blurView.layer.borderColor = UIColor.white.cgColor
blurView.addSubview(numberLabel)
numberLabel.text = "+\(self.userArray.count - 3)"
numberLabel.font = .poppinsMedium(size: 14)
numberLabel.textColor = .white
///LAYOUT
numberImageView.setDimensions(height: self.size, width: self.size)
numberImageView.centerY(inView: self)
numberImageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
numberImageView.bringSubviewToFront(blurView)
blurView.setDimensions(height: self.size, width: self.size)
blurView.center(inView: numberImageView)
numberLabel.center(inView: blurView)
}
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
UIHelper 函数:
extension UIView {
func anchor(top: NSLayoutYAxisAnchor? = nil,
left: NSLayoutXAxisAnchor? = nil,
bottom: NSLayoutYAxisAnchor? = nil,
right: NSLayoutXAxisAnchor? = nil,
paddingTop: CGFloat = 0,
paddingLeft: CGFloat = 0,
paddingBottom: CGFloat = 0,
paddingRight: CGFloat = 0,
width: CGFloat? = nil,
height: CGFloat? = nil) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if let width = width {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if let height = height {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
func center(inView view: UIView, yConstant: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yConstant!).isActive = true
}
func centerX(inView view: UIView, topAnchor: NSLayoutYAxisAnchor? = nil, paddingTop: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
if let topAnchor = topAnchor {
self.topAnchor.constraint(equalTo: topAnchor, constant: paddingTop!).isActive = true
}
}
func centerY(inView view: UIView, leftAnchor: NSLayoutXAxisAnchor? = nil,
paddingLeft: CGFloat = 0, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
if let left = leftAnchor {
anchor(left: left, paddingLeft: paddingLeft)
}
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
}
如何使这个 class 更可靠,以便每次都能获得预期的结果?
我在想也许可以制作一个 UIImageView 数组,然后 return 它到我的集合视图单元格。
你不应该在没有某种形式的同步的情况下在循环中调用异步方法,否则你会产生奇怪的副作用。我敢打赌,这就是您结果不一致的原因。
先下载所有图片,然后在您的视图中显示它们。
您可以使用 combine 非常轻松地获取所有图像。
struct ImageLoader {
let urls: [URL]
func publish() -> AnyPublisher<[UIImage], Error> {
urls.publisher
.flatMap {
URLSession.shared.dataTaskPublisher(for: [=10=])
}
.mapError { [=10=] as Error }
.compactMap { [=10=].data }
.compactMap { UIImage(data: [=10=]) }
.collect()
.eraseToAnyPublisher()
}
}
您可以像这样使用 ImageLoader:
var subscriptions = Set<AnyCancellable>()
func fetchImages(_ urls: [URL], completion: @escaping ([UIImage]) -> Void) {
ImageLoader(urls: urls)
.publish()
.receive(on: DispatchQueue.main)
.sink { result in
print("Result: \(result)")
} receiveValue: { images in
completion(images)
}
.store(in: &subscriptions)
}
至于您的 UserStack,您可以这样做:
class UserStack2: UIView {
var size: CGFloat = 50
init(images: [UIImage]) {
super.init(frame: .zero)
addImages(images)
}
func createImageView(with image: UIImage, spacing: CGFloat) {
let imageView = UIImageView(image: image)
self.addSubview(imageView)
self.bringSubviewToFront(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = self.size * 0.4
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 0.75
imageView.layer.borderColor = UIColor.white.cgColor
imageView.setDimensions(height: self.size, width: self.size)
imageView.centerY(inView: self)
imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
}
func createPlaceHolderView(withValue value: Int, spacing: CGFloat) {
let numberImageView = UIImageView()
let blurView = UIView()
let numberLabel = UILabel()
self.addSubview(numberImageView)
numberImageView.translatesAutoresizingMaskIntoConstraints = false
numberImageView.contentMode = .scaleAspectFill
numberImageView.layer.cornerRadius = self.size * 0.4
numberImageView.layer.masksToBounds = true
//numberImageView.layer.borderWidth = 0.75
//numberImageView.layer.borderColor = UIColor.white.cgColor
numberImageView.addSubview(blurView)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.backgroundColor = .init(white: 0.2, alpha: 0.9)
blurView.layer.cornerRadius = self.size * 0.4
blurView.layer.masksToBounds = true
blurView.layer.borderWidth = 0.75
blurView.layer.borderColor = UIColor.white.cgColor
blurView.addSubview(numberLabel)
numberLabel.text = "+\(value)"
// numberLabel.font = .poppinsMedium(size: 14)
numberLabel.textColor = .white
numberImageView.setDimensions(height: self.size, width: self.size)
numberImageView.centerY(inView: self)
numberImageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
numberImageView.bringSubviewToFront(blurView)
blurView.setDimensions(height: self.size, width: self.size)
blurView.center(inView: numberImageView)
numberLabel.center(inView: blurView)
}
func addImages(_ images: [UIImage]) {
let maxImages = 4
var spacing: CGFloat = 0
images.prefix(maxImages).forEach { image in
createImageView(with: image, spacing: spacing)
spacing += (self.size - 10)
}
if images.count > maxImages {
createPlaceHolderView(withValue: images.count - maxImages, spacing: spacing)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
我 运行 在尝试在集合视图单元格中创建可视元素时遇到了一个奇怪的错误。 class 获取一组 UserModel 并将所有个人资料图片排成一行。如果数组中有超过 4 个用户,最后一个图像视图是带有“+N”的模糊图像,让用户知道还有更多用户无法显示在屏幕上。
实际行为:
预期行为:
当您转到另一个屏幕并返回时,图像会自行更正。
这是我为获得以下结果而创建的 class。
import Foundation
import UIKit
class UserStack: UIView{
var userArray: [UserModel]!
var size: CGFloat = 50
init(userArray: [UserModel]){
super.init(frame: .zero)
self.userArray = userArray
if self.userArray.count <= 4{
fourOrLess()
}else{
fiveOrMore()
}
}
func fourOrLess(){
var spacing: CGFloat = 0
userArray.forEach { user in
let imageView = UIImageView()
guard let url = user.imageURL else {return}
imageView.sd_setImage(with: URL(string: url)) { i, e, c, u in
if let e = e{
print("DEBUG: Error setting userStack - \(e)")
return
}
self.addSubview(imageView)
self.bringSubviewToFront(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = self.size * 0.4
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 0.75
imageView.layer.borderColor = UIColor.white.cgColor
///LAYOUT
imageView.setDimensions(height: self.size, width: self.size)
imageView.centerY(inView: self)
imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
//DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
spacing += (self.size - 10)
// }
}
}
}
func fiveOrMore(){
var count: Int = 1
var spacing: CGFloat = 0
userArray.forEach { user in
if count < 4{
count += 1
let imageView = UIImageView()
guard let url = user.imageURL else {return}
imageView.sd_setImage(with: URL(string: url)) { i, e, c, u in
if let e = e{
print("DEBUG: Error setting userStack - \(e)")
return
}
self.addSubview(imageView)
self.bringSubviewToFront(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = self.size * 0.4
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 0.75
imageView.layer.borderColor = UIColor.white.cgColor
///LAYOUT
imageView.setDimensions(height: self.size, width: self.size)
imageView.centerY(inView: self)
imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
spacing += (self.size - 10)
}
}else{
///NUMBER VIEW
let numberImageView = UIImageView()
let blurView = UIView()
let numberLabel = UILabel()
guard let _URL = userArray.last?.imageURL else {return}
numberImageView.sd_setImage(with: URL(string: _URL)) { i, e, c, u in
if let e = e{
print("DEBUG: Error setting userStack - \(e)")
return
}
self.addSubview(numberImageView)
numberImageView.translatesAutoresizingMaskIntoConstraints = false
numberImageView.contentMode = .scaleAspectFill
numberImageView.layer.cornerRadius = self.size * 0.4
numberImageView.layer.masksToBounds = true
//numberImageView.layer.borderWidth = 0.75
//numberImageView.layer.borderColor = UIColor.white.cgColor
numberImageView.addSubview(blurView)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.backgroundColor = .init(white: 0.2, alpha: 0.9)
blurView.layer.cornerRadius = self.size * 0.4
blurView.layer.masksToBounds = true
blurView.layer.borderWidth = 0.75
blurView.layer.borderColor = UIColor.white.cgColor
blurView.addSubview(numberLabel)
numberLabel.text = "+\(self.userArray.count - 3)"
numberLabel.font = .poppinsMedium(size: 14)
numberLabel.textColor = .white
///LAYOUT
numberImageView.setDimensions(height: self.size, width: self.size)
numberImageView.centerY(inView: self)
numberImageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
numberImageView.bringSubviewToFront(blurView)
blurView.setDimensions(height: self.size, width: self.size)
blurView.center(inView: numberImageView)
numberLabel.center(inView: blurView)
}
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
UIHelper 函数:
extension UIView {
func anchor(top: NSLayoutYAxisAnchor? = nil,
left: NSLayoutXAxisAnchor? = nil,
bottom: NSLayoutYAxisAnchor? = nil,
right: NSLayoutXAxisAnchor? = nil,
paddingTop: CGFloat = 0,
paddingLeft: CGFloat = 0,
paddingBottom: CGFloat = 0,
paddingRight: CGFloat = 0,
width: CGFloat? = nil,
height: CGFloat? = nil) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if let width = width {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if let height = height {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
func center(inView view: UIView, yConstant: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yConstant!).isActive = true
}
func centerX(inView view: UIView, topAnchor: NSLayoutYAxisAnchor? = nil, paddingTop: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
if let topAnchor = topAnchor {
self.topAnchor.constraint(equalTo: topAnchor, constant: paddingTop!).isActive = true
}
}
func centerY(inView view: UIView, leftAnchor: NSLayoutXAxisAnchor? = nil,
paddingLeft: CGFloat = 0, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
if let left = leftAnchor {
anchor(left: left, paddingLeft: paddingLeft)
}
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
}
如何使这个 class 更可靠,以便每次都能获得预期的结果?
我在想也许可以制作一个 UIImageView 数组,然后 return 它到我的集合视图单元格。
你不应该在没有某种形式的同步的情况下在循环中调用异步方法,否则你会产生奇怪的副作用。我敢打赌,这就是您结果不一致的原因。
先下载所有图片,然后在您的视图中显示它们。
您可以使用 combine 非常轻松地获取所有图像。
struct ImageLoader {
let urls: [URL]
func publish() -> AnyPublisher<[UIImage], Error> {
urls.publisher
.flatMap {
URLSession.shared.dataTaskPublisher(for: [=10=])
}
.mapError { [=10=] as Error }
.compactMap { [=10=].data }
.compactMap { UIImage(data: [=10=]) }
.collect()
.eraseToAnyPublisher()
}
}
您可以像这样使用 ImageLoader:
var subscriptions = Set<AnyCancellable>()
func fetchImages(_ urls: [URL], completion: @escaping ([UIImage]) -> Void) {
ImageLoader(urls: urls)
.publish()
.receive(on: DispatchQueue.main)
.sink { result in
print("Result: \(result)")
} receiveValue: { images in
completion(images)
}
.store(in: &subscriptions)
}
至于您的 UserStack,您可以这样做:
class UserStack2: UIView {
var size: CGFloat = 50
init(images: [UIImage]) {
super.init(frame: .zero)
addImages(images)
}
func createImageView(with image: UIImage, spacing: CGFloat) {
let imageView = UIImageView(image: image)
self.addSubview(imageView)
self.bringSubviewToFront(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = self.size * 0.4
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 0.75
imageView.layer.borderColor = UIColor.white.cgColor
imageView.setDimensions(height: self.size, width: self.size)
imageView.centerY(inView: self)
imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
}
func createPlaceHolderView(withValue value: Int, spacing: CGFloat) {
let numberImageView = UIImageView()
let blurView = UIView()
let numberLabel = UILabel()
self.addSubview(numberImageView)
numberImageView.translatesAutoresizingMaskIntoConstraints = false
numberImageView.contentMode = .scaleAspectFill
numberImageView.layer.cornerRadius = self.size * 0.4
numberImageView.layer.masksToBounds = true
//numberImageView.layer.borderWidth = 0.75
//numberImageView.layer.borderColor = UIColor.white.cgColor
numberImageView.addSubview(blurView)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.backgroundColor = .init(white: 0.2, alpha: 0.9)
blurView.layer.cornerRadius = self.size * 0.4
blurView.layer.masksToBounds = true
blurView.layer.borderWidth = 0.75
blurView.layer.borderColor = UIColor.white.cgColor
blurView.addSubview(numberLabel)
numberLabel.text = "+\(value)"
// numberLabel.font = .poppinsMedium(size: 14)
numberLabel.textColor = .white
numberImageView.setDimensions(height: self.size, width: self.size)
numberImageView.centerY(inView: self)
numberImageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
numberImageView.bringSubviewToFront(blurView)
blurView.setDimensions(height: self.size, width: self.size)
blurView.center(inView: numberImageView)
numberLabel.center(inView: blurView)
}
func addImages(_ images: [UIImage]) {
let maxImages = 4
var spacing: CGFloat = 0
images.prefix(maxImages).forEach { image in
createImageView(with: image, spacing: spacing)
spacing += (self.size - 10)
}
if images.count > maxImages {
createPlaceHolderView(withValue: images.count - maxImages, spacing: spacing)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}