RXSwift why loading variable return wrong value? - rx-swift

Here is my view model:
let loadMoreTrigger = PublishSubject<Void>()
let refreshTrigger = PublishSubject<Void>()
let loading = BehaviorRelay<Bool>(value: false)
let stories = BehaviorRelay<[Story]>(value: [])
var offset = 0
let error = PublishSubject<String>()
let selectedFeedType: BehaviorRelay<FeedType> = BehaviorRelay(value: .best)
override init() {
super.init()
let refreshRequest = loading.asObservable().sample(refreshTrigger).flatMap { loading -> Observable<[Story]> in
if loading {
return Observable.empty()
} else {
self.offset = 0
return self.fetchStories(type: self.selectedFeedType.value, offset: self.offset)
}
}
let loadMoreRequest = loading.asObservable().sample(loadMoreTrigger).flatMap { loading -> Observable<[Story]> in
if loading {
return Observable.empty()
} else {
self.offset += 10
return self.fetchStories(type: self.selectedFeedType.value, offset: self.offset)
}
}
let request = Observable.merge(refreshRequest, loadMoreRequest).share(replay: 1)
let response = request.flatMap { (stories) -> Observable<[Story]> in
request.do(onError: { error in
self.error.onNext(error.localizedDescription)
}).catchError { (error) -> Observable<[Story]> in
Observable.empty()
}
}.share(replay: 1)
Observable.combineLatest(request, response, stories.asObservable()) { request, response, stories in
return self.offset == 0 ? response : stories + response
}.sample(response).bind(to: stories).disposed(by: disposeBag)
Observable.merge(request.map{_ in true}, response.map{_ in false}, error.map{_ in false}).bind(to: loading).disposed(by: disposeBag)
}
Then when i checking loading observer i have false -> true, instead of true -> false. I just don't understand why it happening.
loading.subscribe {
print($0)
}.disposed(by: disposeBag)
In my viewController i call refreshTrigger on viewWillAppear using rx.sentMessage
Here is getFeed function:
func getFeed(type: FeedType, offset: Int) -> Observable<[Story]> {
return provider.rx.request(.getFeed(type: type, offset: offset)).asObservable().flatMap { (response) -> Observable<[Story]> in
do {
let feedResponse = try self.jsonDecoder.decode(BaseAPIResponse<[Story]>.self, from: response.data)
guard let stories = feedResponse.data else { return .error(APIError.requestFailed)}
return .just(stories)
} catch {
return .error(error)
}
}.catchError { (error) -> Observable<[Story]> in
return .error(error)
}
}

Your request and response observables are emitting values at the exact same time. Which one shows up in your subscribe first is undefined.
Specifically, request doesn't emit a value until after the fetch request completes. Try this instead:
Observable.merge(
loadMoreTrigger.map { true },
refreshTrigger.map { true },
response.map { _ in false },
error.map { _ in false }
)
.bind(to: loading)
.disposed(by: disposeBag)
There are lots of other problems in your code but the above answers this specific question.

Related

SwiftUI & WidgetKit: Why Intent Handler does not load saved data?

I'm building a Widget with dynamic configuration. I provide a dynamic list of options with an Intents Extension - inside Intent Handler (code below).
However only sampleData shows up, but not the userScrums when tapping on "Edit Widget".
Why it doesn't load userScrums?
import Intents
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
}
extension IntentHandler: ScrumSelectionIntentHandling {
func provideScrumOptionsCollection(for intent: ScrumSelectionIntent) async throws -> INObjectCollection<ScrumType> {
let sampleScrums = DailyScrum.sampleData.map { scrum in
ScrumType(identifier: scrum.title, display: scrum.title)
}
let userScrums = try? await ScrumStore.load().map { scrum in
ScrumType(identifier: scrum.title, display: scrum.title)
}
let allScrums = sampleScrums + (userScrums ?? [])
let collection = INObjectCollection(items: allScrums)
return collection
}
// func provideScrumOptionsCollection(for intent: ScrumSelectionIntent, with completion: #escaping (INObjectCollection<ScrumType>?, Error?) -> Void) {}
}
import Foundation
import SwiftUI
class ScrumStore: ObservableObject {
#Published var scrums: [DailyScrum] = []
private static func fileURL() throws -> URL {
try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appendingPathComponent("scrums.data")
}
static func load() async throws -> [DailyScrum] {
try await withCheckedThrowingContinuation { continuation in
load { result in
switch result {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let scrums):
continuation.resume(returning: scrums)
}
}
}
}
static func load(completion: #escaping (Result<[DailyScrum], Error>)->Void) {
DispatchQueue.global(qos: .background).async {
do {
let fileURL = try fileURL()
guard let file = try? FileHandle(forReadingFrom: fileURL) else {
DispatchQueue.main.async {
completion(.success([]))
}
return
}
let dailyScrums = try JSONDecoder().decode([DailyScrum].self, from: file.availableData)
DispatchQueue.main.async {
completion(.success(dailyScrums))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
#discardableResult
static func save(scrums: [DailyScrum]) async throws -> Int {
try await withCheckedThrowingContinuation { continuation in
save(scrums: scrums) { result in
switch result {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let scrumsSaved):
continuation.resume(returning: scrumsSaved)
}
}
}
}
static func save(scrums: [DailyScrum], completion: #escaping (Result<Int, Error>)->Void) {
DispatchQueue.global(qos: .background).async {
do {
let data = try JSONEncoder().encode(scrums)
let outfile = try fileURL()
try data.write(to: outfile)
DispatchQueue.main.async {
completion(.success(scrums.count))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
}

How can execute operations sequentially and update UI

I'm trying to execute a bunch of operations sequentially and update my UI every time an operation it starts and it finishes (I have to update an operation status icon color).
Below my (working) code:
class SyncManager {
private let disposeBag = DisposeBag()
// MARK: - Private Init
private init() { }
// MARK: - Public Constants
static let shared = SyncManager()
let refreshTokenStatus = BehaviorRelay<SyncStatus>(value: .todo)
let updateCatalogDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let insertDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let getDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
func startDatabaseSync(completion: #escaping ((Result<Void, Error>) -> Void)) {
refreshTokenStatus.accept(.todo)
updateCatalogDataStatus.accept(.todo)
insertDataStatus.accept(.todo)
getDataStatus.accept(.todo)
RefreshTokenManager.shared.refreshToken().do(onSuccess: { [self]_ in
print("RefreshTokenManager onSuccess")
refreshTokenStatus.accept(.completed)
}, onError: { [self] error in
print("RefreshTokenManager onError: \(error)")
refreshTokenStatus.accept(.error)
}, onSubscribe: { [self] in
print("RefreshTokenManager onSubscribe")
refreshTokenStatus.accept(.running)
}).asObservable().concatMap { result in
UpdateCatalogDataSyncManager.shared.updateCatalogData().do(onSuccess: { [self] in
print("UpdateCatalogDataSyncManager onSuccess")
updateCatalogDataStatus.accept(.completed)
}, onError: { [self] error in
print("UpdateCatalogDataSyncManager onError: \(error)")
updateCatalogDataStatus.accept(.error)
}, onSubscribe: { [self] in
print("UpdateCatalogDataSyncManager onSubscribe")
updateCatalogDataStatus.accept(.running)
}).asObservable().concatMap { result in
GetDataSyncManager.shared.getData().do { [self] in
print("GetDataSyncManager onSuccess")
getDataStatus.accept(.completed)
} onError: { [self] error in
print("GetDataSyncManager onError: \(error)")
getDataStatus.accept(.error)
} onSubscribe: { [self] in
print("GetDataSyncManager onSubscribe")
getDataStatus.accept(.running)
} onDispose: {
print("GetDataSyncManager onDispose")
}.asObservable().concatMap { _ in
InsertDataWorkSyncManager.shared.insertData().do { [self] in
print("InsertDataWorkSyncManager onSuccess")
insertDataStatus.accept(.completed)
} onError: { [self] error in
print("InsertDataWorkSyncManager onError: \(error)")
insertDataStatus.accept(.error)
} onSubscribe: { [self] in
print("InsertDataWorkSyncManager onSubscribe")
insertDataStatus.accept(.running)
} onDispose: {
print("InsertDataWorkSyncManager onDispose")
}
}
}
}.subscribe { _ in
print("SyncManager onNext")
} onError: { error in
print("SyncManager onError: \(error)")
completion(.failure(error))
} onCompleted: {
print("SyncManager onCompleted")
completion(.success(()))
} onDisposed: {
print("SyncManager onDisposed")
}.disposed(by: disposeBag)
}
}
enum SyncStatus {
case todo
case completed
case error
case running
case partial
}
My ViewController:
SyncManager.shared.refreshTokenStatus.skip(1).subscribe(onNext: { status in
// Update UI
}).disposed(by: disposeBag)
SyncManager.shared.updateCatalogDataStatus.skip(1).subscribe(onNext: { status in
// Update UI
}).disposed(by: disposeBag)
SyncManager.shared.insertDataStatus.skip(1).subscribe(onNext: { status in
// Update UI
}).disposed(by: disposeBag)
I'm new to RxSwift (I've been using it for only a week) so I would like to know if there's a better approach to achieve my above goal.
Here is an idea that I think will work. It is very imperative conceptually which makes it hard to transcribe to the functional declarative paradigm of FRP. I kept the same external interface so it could be a drop in replacement.
class SyncManager {
private init() { }
static let shared = SyncManager()
let refreshTokenStatus = BehaviorRelay<SyncStatus>(value: .todo)
let updateCatalogDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let insertDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let getDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
private let disposeBag = DisposeBag()
func startDatabaseSync(completion: #escaping (Result<Void, Error>) -> Void) {
let sync = Sync.startDatabaseSync()
disposeBag.insert(
sync.refreshTokenStatus.bind(to: refreshTokenStatus),
sync.updateCatalogDataStatus.bind(to: updateCatalogDataStatus),
sync.insertDataStatus.bind(to: insertDataStatus),
sync.getDataStatus.bind(to: getDataStatus),
sync.getDataStatus.subscribe(
onError: { error in
completion(.failure(error))
},
onCompleted: {
completion(.success(()))
}
)
)
}
}
struct Sync {
let refreshTokenStatus: Observable<SyncStatus>
let updateCatalogDataStatus: Observable<SyncStatus>
let getDataStatus: Observable<SyncStatus>
let insertDataStatus: Observable<SyncStatus>
static func startDatabaseSync() -> Sync {
let refreshTokenStatus = handle(RefreshTokenManager.shared.refreshToken(), after: .just(.completed))
.catchAndReturn(.error)
let updateCatalogDataStatus = handle(UpdateCatalogDataSyncManager.shared.updateCatalogData(), after: refreshTokenStatus)
.catchAndReturn(.error)
let getDataStatus = handle(GetDataSyncManager.shared.getData(), after: updateCatalogDataStatus)
.catchAndReturn(.error)
let insertDataStatus = handle(InsertDataWorkSyncManager.shared.insertData(), after: getDataStatus)
.catchAndReturn(.error)
return Sync(
refreshTokenStatus: refreshTokenStatus,
updateCatalogDataStatus: updateCatalogDataStatus,
getDataStatus: getDataStatus,
insertDataStatus: insertDataStatus
)
}
}
func handle(_ operation: Single<Void>, after: Observable<SyncStatus>) -> Observable<SyncStatus> {
after
.ignoreElements()
.asCompletable()
.andThen(
operation
.map { SyncStatus.completed }
.asObservable()
.startWith(SyncStatus.running)
)
.startWith(.todo)
}
enum SyncStatus {
case todo
case completed
case error
case running
}
If you were to re-arrange the rest of the code to be more in the Rx style, then you could probably make this much cleaner...

Update SwiftUI List after network request using RxSwift

I need to update a SwiftUI List after making a network request. For requests, I use Moya approach with combination of triggers(Input&Output - "Kickstarter").
I cant use Combine framework due to the structure of the project, while they have a lot of helpful advises(not sure about my case).
Simple ContactList:
struct ContactList: View {
var viewModel: UserViewModel
var body: some View {
NavigationView {
List(viewModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}
Then ViewModel
class UserViewModel {
let disposeBag = DisposeBag()
var users: [TeamMember] = []
init(users: [TeamMember] = []) {
let networkModel = UserNetworkModel()
networkModel.output.teamMembers.subscribe { (event) in
self.users.append(contentsOf: event.element.orEmpty)
}.disposed(by: disposeBag)
networkModel.output.error.subscribe(onNext: { error in
print(error.localizedDescription)
}).disposed(by: disposeBag)
networkModel.input.loadTrigger.onNext(Void())
self.users = users
}
}
And NetworkModel
class UserNetworkModel {
let disposeBag = DisposeBag()
let input: Input
let output: Output
struct Input {
let loadTrigger: AnyObserver<Void>
let searchTrigger: AnyObserver<String>
}
struct Output {
let teamMembers: Observable<[TeamMember]>
let error: Observable<Error>
}
internal let loadSubject = PublishSubject<Void>()
internal let searchSubject = BehaviorSubject<String>(value: "")
internal let errorSubject = PublishSubject<Error>()
internal let teamMembersSubject = BehaviorSubject<[TeamMember]>(value: [])
init() {
let service = MoyaProvider<TeamTarget>()
self.input = Input(loadTrigger: loadSubject.asObserver(), searchTrigger: searchSubject.asObserver())
self.output = Output(teamMembers: teamMembersSubject.asObservable(), error: errorSubject.asObservable())
let result = loadSubject.flatMapLatest { _ -> Observable<[TeamMember]> in
service.rx.request(.get).debug().mapArray(TeamMember.self).asObservable()
}.share(replay: 1)
Observable.combineLatest(result, searchSubject).map { (arg) in
let (members, filter) = arg
if filter.isEmpty {
return members
} else {
let searchText = try! self.searchSubject.value()
return members.filter({
return [$0.firstName, $0.lastName]
.compactMap({ $0 })
.first(where: { $0.hasPrefix(searchText) }) != nil
})
}
}.bind(to: teamMembersSubject).disposed(by: disposeBag)
result.subscribe(onError: { error in
self.errorSubject.onNext(error)
}).disposed(by: self.disposeBag)
}
}
Is it possible to update users array in this way? Or only Combine can do it for me easily?
Thanks for your time.
While I've found some Combine solution. Still waiting for Rx solution.
So, I fixed it just added #ObservedObject for ContactList property and #Published for users' array in Network model. Much less code, natively but not what I was looking for.
Full answer:
struct ContactList: View {
#ObservedObject var networkModel: NetworkModel
var body: some View {
NavigationView {
List(networkModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}
#if DEBUG
struct ContactList_Previews: PreviewProvider {
static var previews: some View {
ContactList(networkModel: .init(users: contactData))
}
}
#endif
class NetworkModel: ObservableObject {
#Published var users = [User]()
init(users: [User] = []) {
getMembers()
}
private func getMembers() {
let provider = MoyaProvider<TeamTarget>()
provider.request(.get) { [weak self] (result) in
switch result {
case .success(let response):
do {
let result = try JSONDecoder().decode([User].self, from: response.data)
self?.users.append(contentsOf: result)
} catch {
print(error.localizedDescription)
}
case .failure(let error):
print(error.errorDescription.orEmpty)
}
}
}
}

I want to check text validate when tap button

I want to check text validate when tap button. If validate failure, it will show error text on UILabel and do not send request. Else if validate success, it will send request.
I see many demo about login, but they control button enabled to avoid validate data when tap button. I am puzzled with it.
I have write some code.
class LoginViewModel: BaseViewModel, ViewModelType {
struct Input {
let loginTaps: Driver<Void>
}
struct Output {
let validatedUsername: Driver<Bool>
let validatedPassword: Driver<Bool>
}
let username = BehaviorRelay(value: "")
let password = BehaviorRelay(value: "")
let loginTapped = PublishSubject<Void>()
func transform(input: Input) -> Output {
let validatedUsername = username.asDriver(onErrorJustReturn: "").map { username in
return username.isPhoneNumber
}
let validatedPassword = password.asDriver(onErrorJustReturn: "").map { password in
return password.count > 7
}
input.loginTaps.map { () -> Void in
<#code#>
// I want do check and then do network request
}
input.loginTaps.drive(onNext: { [weak self] () in
self?.loginTapped.onNext(())
}).disposed(by: rx.disposeBag)
loginTapped.flatMapLatest { _ -> Observable<RxSwift.Event<Token>> in
// and if I want to return Bool not Token, how should I do????????????
return self.provider.login(username: self.username.value, password: self.password.value)
.asObservable()
.materialize()
}.subscribe(onNext: { (event) in
switch event {
case .next(let token):
AuthManager.setToken(token: token)
case .error(let error):
log.error(error.localizedDescription)
default: break
}
}).disposed(by: rx.disposeBag)
return Output(validatedUsername: validatedUsername,
validatedPassword: validatedPassword)
}
}
I would expect to see something like this:
class LoginViewModel {
struct Input {
let loginTap: Signal<Void>
let username: Driver<String>
let password: Driver<String>
}
struct Output {
let errorText: Driver<String>
let loginSuccess: Signal<Void>
}
var networkRequest: (URLRequest) -> Observable<Data> = { _ in fatalError("need to replace this with an implementation.") }
func transform(input: Input) -> Output {
func isValidCredentials(username: String, password: String) -> Bool {
return username.isPhoneNumber && password.count > 7
}
let credentials = Driver.combineLatest(input.username, input.password)
// this chain emits when the login button is tapped and the credentials are invalid
let invalidInputError = input.loginTap
.withLatestFrom(credentials)
.filter { !isValidCredentials(username: $0, password: $1) }
.map { _ in "Credentials are invalid" }
.asDriver(onErrorRecover: { _ in fatalError("can't get here") })
let networkRequest = self.networkRequest // to avoid dealing with `self` in the flatMap below
// this chain makes a request if the login button is tapped while the credentials are valid
let loginResult = input.loginTap
.withLatestFrom(credentials)
.filter { isValidCredentials(username: $0, password: $1) }
.map { URLRequest.login(username: $0, password: $1) }
.asObservable()
.flatMapLatest { networkRequest($0).materialize() }
.share(replay: 1)
// this chain emits when the login result produces an error
let loginError = loginResult
.map { $0.error }
.filter { $0 != nil }
.map { $0!.localizedDescription }
.asDriver(onErrorRecover: { _ in fatalError("can't get here") })
// this chain emits when the login result succeeds
let loginSuccess = loginResult
.filter { $0.element != nil }
.map { _ in }
.asSignal(onErrorRecover: { _ in fatalError("can't get here") })
let errorText = Driver.merge(invalidInputError, loginError)
return Output(errorText: errorText, loginSuccess: loginSuccess)
}
}

Stop chain animations and reset on UIPageController page change

I have UIPageViewController with 5 pages. It's on boarding animated UIViewControllers animations on each page. I researched for days and couldn't find the way to stop animations and reset the page after scroll(page change) occurs.
My UIPageViewController is set up like this:
class BoardingPageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
setViewControllers([getStepOne()], direction: .forward, animated: false, completion: nil)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func getStepOne() -> BoardView1ViewController {
return storyboard!.instantiateViewController(withIdentifier: "BoardView1") as! BoardView1ViewController
}
func getStepTwo() -> BoardView2ViewController {
return storyboard!.instantiateViewController(withIdentifier: "BoardView2") as! BoardView2ViewController
}
func getStepThree() -> BoardView3ViewController {
return storyboard!.instantiateViewController(withIdentifier: "BoardView3") as! BoardView3ViewController
}
func getStepFour() -> BoardView4ViewController {
return storyboard!.instantiateViewController(withIdentifier: "BoardView4") as! BoardView4ViewController
}
func getStepFive() -> BoardView5ViewController {
return storyboard!.instantiateViewController(withIdentifier: "BoardView5") as! BoardView5ViewController
}
}
extension BoardingPageViewController : UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if viewController.isKind(of: BoardView5ViewController.self) {
// 5 -> 4
return getStepFour()
} else if viewController.isKind(of: BoardView4ViewController.self) {
// 4 -> 3
return getStepThree()
} else if viewController.isKind(of: BoardView3ViewController.self) {
// 3 -> 2
return getStepTwo()
} else if viewController.isKind(of: BoardView2ViewController.self) {
// 2 -> 1
return getStepOne()
} else {
// 0 -> end of the road
return nil
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController.isKind(of: BoardView1ViewController.self) {
// 0 -> 1
return getStepTwo()
} else if viewController.isKind(of: BoardView2ViewController.self) {
// 1 -> 2
return getStepThree()
} else if viewController.isKind(of: BoardView3ViewController.self) {
// 1 -> 2
return getStepFour()
} else if viewController.isKind(of: BoardView4ViewController.self) {
// 1 -> 2
return getStepFive()
} else {
// 2 -> end of the road
return nil
}
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return 5
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
}
extension BoardingPageViewController : UIPageViewControllerDelegate {
}
And my UIViewControllers are all similar with simple animations. I tried to making this happen with having chainAni value switch to false and call it on ViewDidDisappear. But it doesn't work if page switch happens in the middle of animation.
override func viewDidAppear(_ animated: Bool) {
chainAni = true
ani0()
}
func ani0() {
if chainAni != true {
return
}
UIView.animate(withDuration: 1, delay: 1, animations: {
self.handPic.alpha = 1
}) { (false) in
self.ani1()
}
}
func ani1() {
if chainAni != true {
return
}
UIView.animate(withDuration: 1.5, delay: 1, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.highlightAction(any: self.handPic)
self.highlightAction(any: self.folderBtn)
}) { (false) in
self.ani2()
}
}
func ani2() {
if chainAni != true {
return
}
UIView.animate(withDuration: 1.5, delay: 1, animations: {
self.unhighlightAction(any: self.handPic)
self.unhighlightAction(any: self.folderBtn)
}) { (false) in
self.ani3()
}
}
func ani3() {
if chainAni != true {
return
}
UIView.animate(withDuration: 0.5, delay: 0, animations: {
self.handPic.alpha = 0
self.alertImg.alpha = 1
self.alertImg.transform = .identity
}) { (false) in
self.ani4()
}
}
func clearAni() {
alertImg.image = #imageLiteral(resourceName: "alert1")
darkView.removeFromSuperview()
screenView.removeFromSuperview()
mainImg.alpha = 1
alertImg.alpha = 0
alert2Img.alpha = 0
handPic.alpha = 0
handPic.transform = .identity
hand2Pic.alpha = 0
hand2Pic.transform = .identity
newFolderImg.alpha = 0
newFolderImg.transform = .identity
}
override func viewDidDisappear(_ animated: Bool) {
clearAni()
chainAni = false
}
So, I figured I have to remove every animation from it's superview layer.
func clearAni() {
alertImg.image = #imageLiteral(resourceName: "alert1")
mainImg.layer.removeAllAnimations()
mainLbl.layer.removeAllAnimations()
alertImg.layer.removeAllAnimations()
alert2Img.layer.removeAllAnimations()
folderBtn.layer.removeAllAnimations()
handPic.layer.removeAllAnimations()
hand2Pic.layer.removeAllAnimations()
darkView.layer.removeAllAnimations()
mainImg.alpha = 1
alertImg.alpha = 0
alert2Img.alpha = 0
handPic.alpha = 0
handPic.transform = .identity
hand2Pic.alpha = 0
hand2Pic.transform = .identity
newFolderImg.alpha = 0
newFolderImg.transform = .identity
}

Resources