at present my viewmodel can upload one image but i will like the ability to upload multiple images at once, I have coded that in the image picker just having problems with my upload class. Below is my code:
struct PeopleView: View {
class MainFeedUploadViewModel: ObservableObject {
#ObservedObject var authViewModel = AuthViewModel()
func uploadfeedPost(caption: String, myhashtags: String, image: UIImage) {
if let user = authViewModel.currentUser {
ImageUploader.upLoadImage(image: image, type: .feed) { imageUrl in
let data = [
"caption": caption,
"timestamp": Timestamp(date: Date()),
"likes": 0,
"imageUrl": imageUrl, // here is where i have my issue
"ownerUid": user.id ?? "",
"ownerImageUrl": user.profileImageUrl,
"ownerUsername": user.username
] as [String : Any]
COLLECTION_FEEDS.addDocument(data: data) { _ in
print("DEBUG: Uploaded feed")
}
}
}
}
}
}
I have included the code for the image uploader:
struct ImageUploader {
static func upLoadImage(image: UIImage, type: UploadType, completion: #escaping(String) -> Void) {
guard let imageData = image.jpegData(compressionQuality: 0.5) else { return }
let ref = type.filePath
ref.putData(imageData, metadata: nil) { _, error in
if let error = error {
print("Debug: Failed to upload image with error: \(error.localizedDescription)")
return
}
ref.downloadURL { imageUrl, _ in
guard let imageUrl = imageUrl?.absoluteString else {
return
}
completion(imageUrl)
}
}
}
}
I tried making the UIImage an array with no luck.
I have below a ButtonStyle object that I wish to use when a Button is tapped. However, when I call it on my Button, the effect is seen only on the text within the button and not on the entire Button as a whole.
I have tried calling it outside but to no avail. Can someone please explain to me how I can fix my error?
Button(action: {
}) {
Text("Button")
.foregroundColor(.white)
}.background(Color.red)
.buttonStyle(ScaleButtonStyle())
struct ScaleButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 5 : 1)
}
}
EDIT:
Button(action: {}) {
Text("Button")
.fontWeight(.semibold)
.foregroundColor(.white)
.font(.title)
}
.buttonStyle(ScaleButtonStyle(bgColor: Color.red))
.frame(width: geo.size.width*0.8, height: geo.size.height*0.1, alignment: .center)
.background(Color.red)
.clipShape(Rectangle())
struct ScaleButtonStyle: ButtonStyle {
let bgColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(bgColor)
.scaleEffect(configuration.isPressed ? 5 : 1)
}
}
The solution is to make background as part of a button style as shown below.
Tested with Xcode 11.4 / iOS 13.4
struct ScaleButtonStyle: ButtonStyle {
let bgColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(bgColor)
.scaleEffect(configuration.isPressed ? 5 : 1)
}
}
struct TestScaleButtonStyle: View {
var body: some View {
Button(action: { }) {
Text("Button")
.foregroundColor(.white)
}.buttonStyle(ScaleButtonStyle(bgColor: Color.red))
}
}
How can I have a button perform an action which triggers a function in its 'parent' view? I'm trying to refactor my code so that components are as small as possible.
In this case, the button performs a few tasks, but one of them is to run a function:
Button(
action: {
self.setViewBackToNil()
}){
Text("Button")
}
// which triggers a function
func setViewBackToNil(){
self.userData.image = nil
self.isProcessing = false
.... etc
}
Now, if I turn the button into its own view, I can't pass self.setViewBackToNil because it's contained within the struct of the parent.
Is there a way for a component to trigger a function within its parent?
The best examples on closures and how they can be used is found in the official swift documentation.
This is a small example on how to pass a closure to your child view which then calls a function of the parent:
struct ChildView: View {
var function: () -> Void
var body: some View {
Button(action: {
self.function()
}, label: {
Text("Button")
})
}
}
struct ParentView: View {
var body: some View {
ChildView(function: { self.fuctionCalledInPassedClosure() })
}
func fuctionCalledInPassedClosure() {
print("I am the parent")
}
}
I hope this helps!
Pass a function
And here is an example to pass the function:
struct ChildView: View {
var function: () -> Void
var body: some View {
Button(action: {
self.function()
}, label: {
Text("Button")
})
}
}
struct ParentView: View {
var body: some View {
ChildView(function: self.passedFunction)
}
func passedFunction() {
print("I am the parent")
}
}
Pass a function with parameters
struct ChildView: View {
var myFunctionWithParameters: (String, Int) -> Void
var body: some View {
Button(action: {
self.myFunctionWithParameters("parameter", 1)
}, label: {
Text("Button")
})
}
}
struct ParentView: View {
var body: some View {
ChildView(myFunctionWithParameters: self.passedFunction)
}
func passedFunction(myFirstParameter: String, mySecondParameter: Int) {
print("I am the parent")
}
}
Typically I would use presentTextInputControllerWithSuggestions() to show the TextInput field. But this isn't available in swiftUI because it is a function of WKInterfaceController. Do I have to use the WKInterfaceController for this?
I couldn't find anything in the documentation.
You can use extension for View in SwiftUI:
extension View {
typealias StringCompletion = (String) -> Void
func presentInputController(withSuggestions suggestions: [String], completion: #escaping StringCompletion) {
WKExtension.shared()
.visibleInterfaceController?
.presentTextInputController(withSuggestions: suggestions,
allowedInputMode: .plain) { result in
guard let result = result as? [String], let firstElement = result.first else {
completion("")
return
}
completion(firstElement)
}
}
}
Example:
struct ContentView: View {
var body: some View {
Button(action: {
presentInputController()
}, label: {
Text("Press this button")
})
}
private func presentInputController() {
presentInputController(withSuggestions: []) { result in
// handle result from input controller
}
}
}
This would be done through a TextField in SwiftUI.
watchOS 2 does not have any kind of completion block in its animateWithDuration function. I'm working on a game that requires running code after an animation is completed. Are there any work arounds? Perhaps using key-value observation? The alternative would be to use a timer that matches up with the length of the animation but that's non-ideal for obvious reasons.
NSOperation didn't work for me too. I am just using the following solution for now until Apple officially adds a completion block. Not ideal but it works. I have opened a Radar. Maybe Apple would add a completion block in a future seed of watchOS 2.
This extension adds an animateWithDuration method that takes a completion block. And the completion block is called after the duration of the animation block.
extension WKInterfaceController {
func animateWithDuration(duration: NSTimeInterval, animations: () -> Void, completion: (() -> Void)?) {
animateWithDuration(duration, animations: animations)
let completionDelay = dispatch_time(DISPATCH_TIME_NOW, Int64(duration * Double(NSEC_PER_SEC)))
dispatch_after(completionDelay, dispatch_get_main_queue()) {
completion?()
}
}
}
Try to extend animateWithDuration method with a completion block in your WKInterfaceController
Obj-C version (Thanks #PunnetSethi):
- (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(void))completion{
[self animateWithDuration:duration animations:animations];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), completion);
}
Working on real Watch with WatchOS2
Swift 4
advanced animate() method for watchOS:
Repeatable
Reversible
Stoppable
with completion block
import Foundation
import WatchKit
protocol Animatable: class {
func animate(forKey key: String,duration: TimeInterval, repeatCount count: CGFloat, autoreverses: Bool, animations: #escaping () -> Void, reverseAnimations: (() -> Void)?, completion: (() -> Void)?)
func stopAnimations()
}
extension Animatable {
func animate(forKey key: String, duration: TimeInterval, repeatCount count: CGFloat, autoreverses: Bool, animations: #escaping () -> Void, reverseAnimations: (() -> Void)?, completion: (() -> Void)?) {}
func stopAnimations() {}
}
extension WKInterfaceController: Animatable {
private struct AssociatedKeys {
static var animsDesc = "_animsDesc"
static var stopDesc = "_stopDesc"
}
var animations: [String: (() -> Void)?] {
get {return objc_getAssociatedObject(self, &AssociatedKeys.animsDesc) as? [String: (() -> Void)?] ?? [:]}
set {objc_setAssociatedObject(self, &AssociatedKeys.animsDesc, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
}
var animationStopStates: [String: Bool] {
get {return objc_getAssociatedObject(self, &AssociatedKeys.stopDesc) as? [String: Bool] ?? [:]}
set {objc_setAssociatedObject(self, &AssociatedKeys.stopDesc, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
}
func animate(forKey key: String, duration: TimeInterval, repeatCount count: CGFloat = 1.0, autoreverses: Bool = false, animations: #escaping () -> Void, reverseAnimations: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
let isStopped = self.animationStopStates[key] ?? false
if isStopped {
completion?()
return
}
self.setAnimations(key, reverse: reverseAnimations)
let count = count - 1
let deadline = DispatchTime.now() + duration
self.animate(withDuration: duration, animations: animations)
DispatchQueue.main.asyncAfter(deadline: deadline) {
var deadline2 = DispatchTime.now()
if autoreverses, let rev = reverseAnimations {
self.animate(withDuration: duration, animations: rev)
deadline2 = DispatchTime.now() + duration
}
DispatchQueue.main.asyncAfter(deadline: deadline2, execute: {
if !count.isEqual(to: .infinity) && count <= 0 {
completion?()
} else {
self.animate(forKey: key, duration: duration, repeatCount: CGFloat(count), autoreverses: autoreverses, animations: animations, reverseAnimations: reverseAnimations, completion: completion)
}
})
}
}
/// Stops all the currently playing animations
func stopAnimations() {
for key in self.animations.keys {
guard let rev = self.animations[key] else {return}
self.animationStopStates[key] = true
self.animate(forKey: key, duration: 0, repeatCount: 0, autoreverses: false, animations: {}, reverseAnimations: rev)
}
self.animations.removeAll()
}
private func setAnimations(_ key: String, reverse: (() -> Void)?) {
if self.animations[key] == nil {
self.animations[key] = reverse
}
}
}
How to use:
self.animate(forKey: "custom_anim1", duration: 1.0, repeatCount: .infinity, autoreverses: true, animations: {[unowned self] in
self.myButton.setHeight(100)
}, reverseAnimations: { [unowned self] in
self.myButton.setHeight(120)
}) {
print("Animation stopped!")
}
To stop all the animations:
self.stopAnimations()
You can use this extension. It's a better solutions because it doesn't rely on time like dispatching a block of code after the same amount of time as your animation with a delay. And it's elegant.
extension WKInterfaceController {
func animate(withDuration duration: TimeInterval,
animations: #escaping () -> Swift.Void,
completion: #escaping () -> Swift.Void) {
let queue = DispatchGroup()
queue.enter()
let action = {
animations()
queue.leave()
}
self.animate(withDuration: duration, animations: action)
queue.notify(queue: .main, execute: completion)
}
}
And you can use it, easily like this:
self.animate(withDuration: 0.2, animations: {
//your animation code
}, completion: {
//to do after you animation
})