Maundytime
Maundytime

Reputation: 111

Trouble to make a custom UIView aspect scale fit/fill with SwiftUI

No Public API in SwiftUI to response for the resizable modifier of View protocol. Only Image in SwiftUI could work with .resizable(). Custom UIView like UIView for GIF is not resizable now.

I use SDWebImageSwiftUI AnimatedImage, which is backing UIKit View SDAnimatedImageView. AnimatedImage is not response to .resizable(), .scaleToFit, .aspectRatio(contentMode: .fit), etc. WebImage is backing SwiftUI Image, so it's working fine.

import SwiftUI
import SDWebImageSwiftUI

struct ContentView: View {
    let url = URL(string: "https://media.giphy.com/media/H62DGtBRwgbrxWXh6t/giphy.gif")!
    var body: some View {
        VStack {
            AnimatedImage(url: url)
                .scaledToFit()
                .frame(width: 100, height: 100)
            WebImage(url: url)
                .scaledToFit()
                .frame(width: 100, height: 100)
        }
    }
}

Not sure if it's an Apple bug. Expect custom view like SDWebImageSwiftUI AnimatedImage is responsive to SwiftUI size related modifiers like .scaledToFit().

Related issue: https://github.com/SDWebImage/SDWebImageSwiftUI/issues/3

Upvotes: 4

Views: 2635

Answers (2)

Tomáš Linhart
Tomáš Linhart

Reputation: 14299

SwiftUI uses the compression resistance priority and the content hugging priority to decide what resizing is possible.

If you want to resize a view below its intrinsic content size, you need to reduce the compression resistance priority.

Example:

func makeUIView(context: Context) -> UIView {
    let imageView = UIImageView(image: UIImage(named: "yourImage")!)
    imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
    return imageView
}

This will allow you to set .frame(width:height:) to any size you want.

Upvotes: 15

Maundytime
Maundytime

Reputation: 111

Finally found a solution.

Make a UIView wrapper outside of the SDAnimationImageView or UIImageView, then override layoutSubviews() set the frame of subview.

Here is full code by me.

And SDWebImageSwiftUI also release a new version which uses wrapper to solve this problem.

class ImageModel: ObservableObject {
    @Published var url: URL?
    @Published var contentMode: UIView.ContentMode = .scaleAspectFill
}

struct WebImage: UIViewRepresentable {
    @ObservedObject var imageModel = ImageModel()

    func makeUIView(context: UIViewRepresentableContext<WebImage>) -> ImageView {
        let uiView = ImageView(imageModel: imageModel)
        return uiView
    }

    func updateUIView(_ uiView: ImageView, context: UIViewRepresentableContext<WebImage>) {
        uiView.imageView.sd_setImage(with: imageModel.url)
        uiView.imageView.contentMode = imageModel.contentMode
    }

    func url(_ url: URL?) -> Self {
        imageModel.url = url
        return self
    }

    func scaledToFit() -> Self {
        imageModel.contentMode = .scaleAspectFit
        return self
    }

    func scaledToFill() -> Self {
        imageModel.contentMode = .scaleAspectFill
        return self
    }
}

class ImageView: UIView {
    let imageView = UIImageView()

    init(imageModel: ImageModel) {
        super.init(frame: .zero)
        addSubview(imageView)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Upvotes: 1

Related Questions