I am trying to create a custom SwiftUI View but I am getting problem in updating value through ObservedObject.
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var model:InfoViewModel = InfoViewModel()
var body: some View {
VStack(alignment: .leading){
CustomView(value: self.model.title)
.frame(width: 200, height: 200, alignment: .center)
}
.background(Color.green)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CustomView: UIViewRepresentable {
typealias UIViewType = UIView
var value:String
var lblTitle:UILabel = UILabel()
func makeUIView(context: Context) -> UIView {
let view:UIView = UIView()
view.addSubview(lblTitle)
lblTitle.text = value
lblTitle.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
lblTitle.backgroundColor = UIColor.red
lblTitle.textColor = UIColor.black
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
self.lblTitle.text = value
print("LBLTitle:\(self.lblTitle.text!)\nTitleValue:\(self.value)")
}
}
class InfoViewModel: ObservableObject, Identifiable {
#Published var title = "Title"
private var count = 0
init() {
_ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
}
#objc func fireTimer() {
count += 1
self.title = "\(self.count)"
print("Timer fired!\n\(self.title)")
}
}
Project can be download from here
Edit:
I changed code after #Asperi answer now it is updating value. but as Dávid Pásztor suggested it is not right to create #ObservedObject inside struct then how I will pass the value to CustomView with creating #ObservedObject and link that David provided is old and code in that thread now seems not working.
Here is the code that is working
struct CustomView: UIViewRepresentable {
typealias UIViewType = UIView
var value:String
func makeUIView(context: Context) -> UIView {
let view:UIView = UIView()
let lblTitle:UILabel = UILabel()
view.addSubview(lblTitle)
lblTitle.text = value
lblTitle.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
lblTitle.backgroundColor = UIColor.red
lblTitle.textColor = UIColor.black
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
for view in uiView.subviews
{
if(view.isKind(of: UILabel.self))
{
(view as! UILabel).text = value
print("LBLTitle:\((view as! UILabel).text!)\nTitleValue:\(self.value)")
}
}
}
}
You don't need to keep it as property because struct can be recreated (so you loose it), instead you have access your view via provided in arguments.
Note: UIViewRepresentable handles reference to corresponding view by itself.
struct CustomView: UIViewRepresentable {
typealias UIViewType = UILabel
var value:String
func makeUIView(context: Context) -> UILabel {
let lblTitle = UILabel()
lblTitle.text = value
lblTitle.backgroundColor = UIColor.red
lblTitle.textColor = UIColor.black
return lblTitle
}
func updateUIView(_ uiLabel: UILabel, context: Context) {
uiLabel.text = value
}
}
Related
I'm learning about PencilKit.
I have a canvas, and I want to set a background image that we can draw on it.
When I save my canvas, I want my background image to be visible
But I have an error :
Cannot convert value of type 'Image' to expected argument type 'UIImage?'
Image("badmintoncourt") is an image from my assets
I can't find out how to solve it, but I maybe not in the right way to add a background image to my canvas
struct Home : View {
#State var canvas = PKCanvasView()
#Environment(\.undoManager) private var undoManager
#State var showingAlert = false
var body: some View{
NavigationView{
MyCanvas(canvasView: canvas)
.navigationTitle("Drawing")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct MyCanvas: UIViewRepresentable {
var canvasView: PKCanvasView
let picker = PKToolPicker.init()
func makeUIView(context: Context) -> PKCanvasView {
self.canvasView.tool = PKInkingTool(.pen, color: .black, width: 15)
self.canvasView.isOpaque = false
self.canvasView.backgroundColor = UIColor.clear
self.canvasView.becomeFirstResponder()
let imageView = Image("badmintoncourt")
let subView = self.canvasView.subviews[0]
subView.addSubview(imageView)
subView.sendSubviewToBack(imageView)
return canvasView
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
picker.addObserver(canvasView)
picker.setVisible(true, forFirstResponder: uiView)
DispatchQueue.main.async {
uiView.becomeFirstResponder()
}
}
}
Edit :
Here is my code to save image :
func SaveImage(){
let image = canvas.drawing.image(from: canvas.drawing.bounds, scale: 1)
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
The Image is not a UIView, you have to use UIImageView for this case
let imageView = UIImageView(image: UIImage(named: "badmintoncourt"))
let subView = self.canvasView.subviews[0]
subView.addSubview(imageView)
subView.sendSubviewToBack(imageView)
This is how I solved this problem to allow drawing over the image and saving both in one final image. This solution makes the canvas for drawing exactly the size of the image.
I add the canvas as an overlay for the Image and store the drawing in seperate image. Once I'm done with my drawing, I merge both images into one.
struct DrawOnImageView: View {
#Binding var image: UIImage
let onSave: (UIImage) -> Void
#State private var drawingOnImage: UIImage = UIImage()
#State private var canvasView: PKCanvasView = PKCanvasView()
init(image: Binding<UIImage>, onSave: #escaping (UIImage) -> Void) {
self.image = image
self.onSave = onSave
}
var body: some View {
VStack {
Button(action: { save() }, label: Text("Save"))
Image(uiImage: self.image)
.resizable()
.aspectRatio(contentMode: .fit)
.edgesIgnoringSafeArea(.all)
.overlay(CanvasView(canvasView: $canvasView, onSaved: onChanged), alignment: .bottomLeading)
}
}
private func onChanged() -> Void {
self.drawingOnImage = canvasView.drawing.image(
from: canvasView.bounds, scale: UIScreen.main.scale)
}
private func initCanvas() -> Void {
self.canvasView = PKCanvasView();
self.canvasView.isOpaque = false
self.canvasView.backgroundColor = UIColor.clear
self.canvasView.becomeFirstResponder()
}
private func save() -> Void {
onSave(self.image.mergeWith(topImage: drawingOnImage))
}
}
This extension to UIImage will allow you to merge images. I used the code from this answer How to merge two UIImages?
public extension UIImage {
func mergeWith(topImage: UIImage) -> UIImage {
let bottomImage = self
UIGraphicsBeginImageContext(size)
let areaSize = CGRect(x: 0, y: 0, width: bottomImage.size.width, height: bottomImage.size.height)
bottomImage.draw(in: areaSize)
topImage.draw(in: areaSize, blendMode: .normal, alpha: 1.0)
let mergedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return mergedImage
}
}
Finally, this is my canvas view, even though I think MyCanvas in your code should work just fine. My view is based from this PencilKit tutorial.
struct CanvasView {
#Binding var canvasView: PKCanvasView
let onSaved: () -> Void
#State var toolPicker = PKToolPicker()
}
extension CanvasView: UIViewRepresentable {
func makeUIView(context: Context) -> PKCanvasView {
canvasView.tool = PKInkingTool(.pen, color: .gray, width: 10)
#if targetEnvironment(simulator)
canvasView.drawingPolicy = .anyInput
#endif
canvasView.delegate = context.coordinator
showToolPicker()
return canvasView
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(canvasView: $canvasView, onSaved: onSaved)
}
}
private extension CanvasView {
func showToolPicker() {
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
}
class Coordinator: NSObject {
var canvasView: Binding<PKCanvasView>
let onSaved: () -> Void
init(canvasView: Binding<PKCanvasView>, onSaved: #escaping () -> Void) {
self.canvasView = canvasView
self.onSaved = onSaved
}
}
extension Coordinator: PKCanvasViewDelegate {
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
if !canvasView.drawing.bounds.isEmpty {
onSaved()
}
}
}
I'm having an issue while moving a view that contains a SwiftUI view near the edges of the screen. The SwiftUI view moves itself to avoid being blocked by the safe area insets
I'm using UIKit to handle dragging the view via UIHostingController and a UIPanGestureRecognizer. Here's the code
import UIKit
class ViewController: UIViewController {
var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let contentVc = UIHostingController(rootView: Content())
addChild(contentVc)
contentView = contentVc.view
let contentHeight = contentView.sizeThatFits(view.bounds.size).height
contentView.frame = CGRect(x: 0, y: 200, width: view.bounds.width, height: contentHeight)
view.addSubview(contentView)
contentVc.didMove(toParent: self)
let drag = UIPanGestureRecognizer(target: self, action: #selector(drag(_:)))
contentView.addGestureRecognizer(drag)
view.backgroundColor = .orange
}
var startingPoint = CGPoint.zero
#objc func drag(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
startingPoint = contentView.frame.origin
case .changed:
let location = gesture.translation(in: view)
contentView.frame.origin.y = startingPoint.y + location.y
default:
break
}
}
}
import SwiftUI
struct Content: View {
var body: some View {
VStack {
ForEach(0..<10) { i in
Text(String(i))
}
}
.background(Color.blue)
.padding(.vertical, 32)
.ignoresSafeArea()
}
}
What I'm expecting is the blue view to not to adjust itself vertically (in the preview the blue view's top padding is shrinking)
I found 2 ways for solving this issue:
Way1: Using UIKit Gesture!
class ViewController: UIViewController {
var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let contentVc = UIHostingController(rootView: Content())
addChild(contentVc)
contentView = contentVc.view
let contentHeight = contentView.sizeThatFits(view.bounds.size).height
contentView.frame = CGRect(x: 0, y: 100, width: view.bounds.width, height: contentHeight)
contentView.backgroundColor = .clear
view.addSubview(contentView)
contentVc.didMove(toParent: self)
let drag = UIPanGestureRecognizer(target: self, action: #selector(drag(_:)))
contentView.addGestureRecognizer(drag)
view.backgroundColor = .orange
}
var startingPoint = CGPoint.zero
#objc func drag(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
startingPoint = contentView.frame.origin
case .changed:
let location = gesture.translation(in: view)
contentView.frame.origin.y = startingPoint.y + location.y
default:
break
}
}
}
import SwiftUI
struct Content: View {
var body: some View {
VStack {
ForEach(0..<10) { i in
Text(String(i))
}
}
.padding()
.background(Color.blue)
.frame(maxWidth: .infinity)
.background(Color.white)
}
}
Way2: Using SwiftUI Gesture!
class ViewController: UIViewController {
var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let contentVc = UIHostingController(rootView: Content())
addChild(contentVc)
contentView = contentVc.view
contentView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
contentView.backgroundColor = .clear
view.addSubview(contentView)
contentVc.didMove(toParent: self)
view.backgroundColor = .orange
}
}
struct Content: View {
#State private var offset: CGFloat = .zero
#State private var lastOffset: CGFloat = .zero
var body: some View {
VStack {
ForEach(0..<10) { i in
Text(String(i))
}
}
.padding()
.background(Color.blue)
.frame(maxWidth: .infinity)
.background(Color.white)
.offset(y: offset)
.gesture(DragGesture(minimumDistance: .zero, coordinateSpace: .local).onChanged { value in
offset = lastOffset + value.translation.height
}
.onEnded { value in
lastOffset = lastOffset + value.translation.height
offset = lastOffset
})
}
}
Need to display html formatted text inside the tableview in SwiftUI but Text("Hi") is not allowing us to use Attributed text inside it.
So trying following code to display multiline HTML formatted text inside List. But with no success.
And cell height should be dynamic according to the content size height.
struct ContentView: View {
#State var text = "Hello World This is line with more than two line of code to display as multiline textview inside the List cell."
#State var bool: Bool = false
var body: some View {
List(0 ..< 5) { item in
TextView(text: self.$text)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
}
struct TextView: UIViewRepresentable {
#Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let myTextView = UITextView()
myTextView.delegate = context.coordinator
myTextView.font = UIFont(name: "HelveticaNeue", size: 15)
myTextView.isScrollEnabled = false
myTextView.isEditable = false
myTextView.isUserInteractionEnabled = true
myTextView.backgroundColor = .clear //UIColor(white: 0.0, alpha: 0.05)
myTextView.translatesAutoresizingMaskIntoConstraints = false
myTextView.isScrollEnabled = false
return myTextView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: TextView
init(_ uiTextView: TextView) {
self.parent = uiTextView
}
// with delegate methods implemented normally
}
}
how about if you do this:
myTextView.isScrollEnabled = true
does this work?
Try below code hope it will help you.
(for any correction you want please put comment)
struct ContentView: View {
let text = "<p>This is<br>a paragraph<br>with line breaks. This is <sup>superscripted</sup> text.</p>"
#State var bool: Bool = false
var body: some View {
List(0..<5) { item in
Text(self.text.htmlToAttributedString.string)
.font(Font.system(size: 17))
.multilineTextAlignment(.leading)
}
}
}
Extension
extension String {
var htmlToAttributedString: NSAttributedString {
guard let data = data(using: .utf8) else { return NSAttributedString() }
do {
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return NSAttributedString()
}
}
}
Output
Couldn't get for now the main link, from where I got this but it works as I needed.
ScrollView {
VStack {
ForEach(/*your implementation*/) { item in
UITextViewWrapper(text: self.$text, calculatedHeight: self.$size, onDone: nil)
}
.frame(minWidth: 0, maxWidth: proxy.size.width, minHeight: self.size, maxHeight: .infinity)
}
}
fileprivate struct UITextViewWrapper: UIViewRepresentable {
typealias UIViewType = UITextView
#Binding var text: String
#Binding var calculatedHeight: CGFloat
var onDone: (() -> Void)?
func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
let textField = UITextView()
textField.delegate = context.coordinator
textField.isEditable = false
textField.font = UIFont.preferredFont(forTextStyle: .body)
textField.isSelectable = false
textField.isUserInteractionEnabled = false
textField.isScrollEnabled = true
textField.backgroundColor = UIColor.clear
if nil != onDone {
textField.returnKeyType = .done
}
// textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textField
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
}
fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>)
{
let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
if result.wrappedValue != newSize.height
{
DispatchQueue.main.async
{
result.wrappedValue = newSize.height // !! must be called asynchronously
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone)
// return Coordinator()
}
final class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
var calculatedHeight: Binding<CGFloat>
var onDone: (() -> Void)?
init(text: Binding<String>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) {
self.text = text
self.calculatedHeight = height
self.onDone = onDone
}
func textViewDidChange(_ uiView: UITextView) {
text.wrappedValue = uiView.text
UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if let onDone = self.onDone, text == "\n" {
textView.resignFirstResponder()
onDone()
return false
}
return true
}
}
}
Starting from iOS 15 you can use AttributedString or Markdown with Text. And so you get multiline formatted text and dynamic cell height.
An example of using markdown:
Text("This text is **bold**, this is *italic*. This is a tappable link: [tap here](https://stackoverflow.com)")
AttributedString gives you more control over formatting. For example, you can change a link color or underline color:
var string = AttributedString("")
// you can use markdown with AttributedString
let markdownText = try! AttributedString(markdown: "This is **bold** text. This is *italic* text.")
var tappableText = AttributedString(" I am tappable! ")
tappableText.link = URL(string: "https://stackoverflow.com")
tappableText.foregroundColor = .green
var underlinedText = AttributedString("This is underlined text.")
underlinedText.underlineStyle = Text.LineStyle(pattern: .solid, color: .red)
string.append(markdownText)
string.append(tappableText)
string.append(underlinedText)
Text(string)
Here is what it looks like:
When I'm trying to sign in and on the next view sign out with GIDSignIn and navigate to previous view everything is fine, but when I'm trying to sign in again, the alert ask App wants to use google sign in, when I press continue - I have an error says:
Keyboard cannot present view controllers (attempted to present )
and the next error
First responder error: non-key window attempting reload - allowing due to manual keyboard (first responder window is >, key window is ; layer = >)
My code
import SwiftUI
import Foundation
import GoogleSignIn
struct LoginView: View {
#ObservedObject var loginViewModel = LoginViewModel()
var body: some View {
NavigationView {
VStack {
Button(action: SocialLogin().attemptLoginGoogle, label: {
HStack{
Image("google")
Text("Google login")
.font(.title)
}
})
.padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
.background(Color.white)
.cornerRadius(8.0)
.shadow(radius: 4.0)
NavigationLink(destination: UserData(), isActive: self.$loginViewModel.isLogedIn) {
EmptyView()
}
}
.navigationBarTitle(Text("Login"))
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
struct SocialLogin: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<SocialLogin>) -> UIView {
return UIView()
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<SocialLogin>) {
}
func attemptLoginGoogle() {
GIDSignIn.sharedInstance()?.presentingViewController = UIApplication.shared.windows.last?.rootViewController
GIDSignIn.sharedInstance()?.signIn()
}
}
I encounter this problem in SwiftUI.
and I use this code.
if(GIDSignIn.sharedInstance()?.presentingViewController==nil){
GIDSignIn.sharedInstance()?.presentingViewController = UIApplication.shared.windows.last?.rootViewController
}
GIDSignIn.sharedInstance()?.signIn()
change
GIDSignIn.sharedInstance()?.presentingViewController = UIApplication.shared.windows.last?.rootViewController
to
GIDSignIn.sharedInstance()?.presentingViewController = UIApplication.shared.windows.first?.rootViewController
So the problem was in
GIDSignIn.sharedInstance()?.presentingViewController = UIApplication.shared.windows.last?.rootViewController
You have to present in ViewController (in my case UIRepresentableViewController worked) otherwise it will tell you that keyboard can't show or present new View
Wrapper:
struct WrapedViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> LoginViewController {
let vc = LoginViewController()
print("\nmakeUIViewController \(vc)")
return vc
}
func updateUIViewController(_ uiViewController: LoginViewController, context: Context) {
print("updateUIViewController \(uiViewController)")
}
static func dismantleUIViewController(_ uiViewController: LoginViewController, coordinator: Self.Coordinator) {
print("dismantleUIViewController \(uiViewController)")
}
}
Wraped ViewController:
class LoginViewController: UIViewController {
override func viewDidLoad() {
let screenWidth = self.view.frame.size.width
let screenHeight = self.view.frame.size.height
let height: CGFloat = 40.0
let width: CGFloat = 120.0
let button = UIButton(frame: CGRect(x: (screenWidth / 2.0) - (width / 2.0),
y: (screenHeight / 2.0) - (height / 2.0),
width: width,
height: height))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func buttonAction(sender: UIButton!) {
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance()?.signIn()
}
}
I want to animate images in SwiftUI's Image view
First, I tried creating some variables and a function to toggle the Image("imageVariable"). It changes but there is no animation even tried the withAnimation { } method
Secondly, I tried to use a UIKit view. Here, the animation works but I can't apply the resizable() modifier or a set a fixed frame
var images: [UIImage]! = [UIImage(named: "pushup001")!, UIImage(named: "pushup002")!]
let animatedImage = UIImage.animatedImage(with: images, duration: 0.5)
struct workoutAnimation: UIViewRepresentable {
func makeUIView(context: Self.Context) -> UIImageView {
return UIImageView(image: animatedImage)
}
func updateUIView(_ uiView: UIImageView, context: UIViewRepresentableContext<workoutAnimation>) {
}
}
struct WorkoutView: View {
var body: some View {
VStack {
workoutAnimation().aspectRatio(contentMode: .fit)
}
}
}
In method 1 I can change the image but not animate, while, in method 2 I can animate but not control it's size
I solved this using UIViewRepresentable protocol. Here I returned a UIView with the ImageView as it's subview. This gave me more control over the child's size, etc.
import SwiftUI
var images : [UIImage]! = [UIImage(named: "pushup001")!, UIImage(named: "pushup002")!]
let animatedImage = UIImage.animatedImage(with: images, duration: 0.5)
struct workoutAnimation: UIViewRepresentable {
func makeUIView(context: Self.Context) -> UIView {
let someView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
let someImage = UIImageView(frame: CGRect(x: 20, y: 100, width: 360, height: 180))
someImage.clipsToBounds = true
someImage.layer.cornerRadius = 20
someImage.autoresizesSubviews = true
someImage.contentMode = UIView.ContentMode.scaleAspectFill
someImage.image = animatedImage
someView.addSubview(someImage)
return someView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<workoutAnimation>) {
}
}
struct WorkoutView: View {
var body: some View {
VStack (alignment: HorizontalAlignment.center, spacing: 10) {
workoutAnimation()
Text("zzzz")
}
}
}
If you want a robust and cross-platform SwiftUI implementation for animated images, like GIF/APNG/WebP, I recommend using SDWebImageSwiftUI. This framework is based on exist success image loading framework SDWebImage and provides a SwiftUI binding.
To play the animation, use AnimatedImage view.
var body: some View {
Group {
// Network
AnimatedImage(url: URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"))
.onFailure(perform: { (error) in
// Error
})
}
}
in model :
var publisher : Timer?
#Published var index = 0
func startTimer() {
index = 0
publisher = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: {_ in
if self.index < count/*count of frames*/{
self.index += 1
}
else if let timer = self.publisher {
timer.invalidate()
self.publisher = nil
}
})
}
}
in view :
struct MyAnimationView : View {
let width : CGFloat
let images = (0...60).map { UIImage(named: "tile\($0)")! }
#StateObject var viewmodel : MyViewModel
var body: some View {
Image(uiImage: images[viewmodel.index])
.resizable()
.frame(width: width, height: width, alignment: .center)
}
}
I have created an image animation class that can be easily reused
import SwiftUI
struct ImageAnimated: UIViewRepresentable {
let imageSize: CGSize
let imageNames: [String]
let duration: Double = 0.5
func makeUIView(context: Self.Context) -> UIView {
let containerView = UIView(frame: CGRect(x: 0, y: 0
, width: imageSize.width, height: imageSize.height))
let animationImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height))
animationImageView.clipsToBounds = true
animationImageView.layer.cornerRadius = 5
animationImageView.autoresizesSubviews = true
animationImageView.contentMode = UIView.ContentMode.scaleAspectFill
var images = [UIImage]()
imageNames.forEach { imageName in
if let img = UIImage(named: imageName) {
images.append(img)
}
}
animationImageView.image = UIImage.animatedImage(with: images, duration: duration)
containerView.addSubview(animationImageView)
return containerView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<ImageAnimated>) {
}
}
The way to use it:
ImageAnimated(imageSize: CGSize(width: size, height: size), imageNames: ["loading1","loading2","loading3","loading4"], duration: 0.3)
.frame(width: size, height: size, alignment: .center)