import SwiftUI
import PhotosUI
import AVKit
import Swime
import WebKit
struct GifImage: UIViewRepresentable {
enum URLType {
case local(String)
case remote(URL?)
var url: URL? {
switch self {
case .local(let name):
return Bundle.main.url(forResource: name, withExtension: "gif")
case .remote(let url):
return url
}
}
}
let type: URLType
var loaded: ((UIImageView, UIImage?) -> Void)?
func makeUIView(context: Context) -> UIView {
let view = UIImageView()
DispatchQueue.global(qos: .background).async {
if let url = type.url, let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
if let image = UIImage.gif(data: data) {
view.image = image
view.contentMode = .scaleAspectFit
self.loaded?(view, image)
}
}
} else {
DispatchQueue.main.async {
self.loaded?(view, nil)
}
}
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
struct VidPlay: View {
@State var gifSize: CGSize?
@State var gifHeight: CGFloat = 80
var body: some View {
VStack(spacing: 20){
ZStack{
GifImage(type: .remote(URL(string: "https://media.tenor.com/hRiPtsp-m0IAAAAM/the-simpsons-homer-simpson.gif"))) { _, image in
gifSize = image?.size
}
.frame(width: 80, height: gifHeight)
}
.onChange(of: gifSize) { _ in
if(gifSize != nil){
gifHeight = 80 * (gifSize!.height / gifSize!.width)
print(gifHeight)
}
}
.frame(width: 240, height: 240)
.background(Color.blue)
}
}
}
It prints out the correct gifHeight
, but the frame size is getting ignored, how to fix this?
SwiftUI views can all choose their own sizes based on a ProposedViewSize
, and they can totally choose a size that is larger than the proposal. .frame
merely changes the proposed size, but the UIViewRepresentable
chose a size that fits its container, as a default.
You can implement sizeThatFits
in your UIViewRepresentable
:
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIImageView, context: Context) -> CGSize? {
guard let imageSize = uiView.image?.size else {
return nil
}
return if let width = proposal.width, let height = proposal.height {
CGSize(width: width, height: height)
} else if let width = proposal.width {
CGSize(width: width, height: width * imageSize.height / imageSize.width)
} else if let height = proposal.height {
CGSize(width: height * imageSize.width / imageSize.height, height: height)
} else {
nil
}
}
Note that I used UIImageView
in the signature of sizeThatFits
- you should also change the signature of makeUIView
and updateUIView
, replacing UIView
with UIImageView
too.
Now all you need on the SwiftUI side is .frame(width: 80)
. You don't need to track the image's size anymore.
Another way is to override the image view's intrinsic content size in a subclass, and use fixedSize()
to force SwiftUI to use its intrinsic content size.
class MyImageView: UIImageView {
var size: CGSize = .zero
override var intrinsicContentSize: CGSize {
size
}
}
struct GifImage: UIViewRepresentable {
enum URLType {
// ...
}
let type: URLType
let size: CGSize
var loaded: ((UIImageView, UIImage?) -> Void)?
func makeUIView(context: Context) -> MyImageView {
let view = MyImageView()
DispatchQueue.global(qos: .background).async {
// ...
}
return view
}
func updateUIView(_ uiView: MyImageView, context: Context) {
uiView.size = size
uiView.invalidateIntrinsicContentSize()
}
}
// usage:
let url = URL(string: "https://media.tenor.com/hRiPtsp-m0IAAAAM/the-simpsons-homer-simpson.gif")
GifImage(type: .remote(url), size: .init(width: 80, height: gifHeight)) { _, image in
gifSize = image?.size
}
.fixedSize()