How to call a function in a different class in swift - swift

I have a MainCoordinator class extending NSObject and having the following methods:
init(presenter: UINavigationController) {
self.presenter = presenter
}
​func start() {
let mainViewController = MainViewController(userDefaults: UserDefaults.standard)
presenter.pushViewController(mainViewController, animated: false)
self.mainViewController = mainViewController
subscribeToEvents()
}
private func subscribeToEvents() {
if let viewModel = mainViewController?.viewModel {
viewModel.showOptions.subscribe(onNext: { [weak self] in
self?.showOptions()
}).disposed(by: disposeBag)
}
}
private func showOptions() {
let actionSheet = UIAlertController(title: "Would you like to open the Camera or select one from your photo library?",
message: nil,
preferredStyle: .actionSheet)
let cameraAction = UIAlertAction(title: "Use the Camera", style: .default) { [weak self] (_) in
self?.openCameraScanner()
}
let photoLibraryAction = UIAlertAction(title: "Open Photo Library", style: .default) { [weak self] (_) in
self?.openPhotoLibrary()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
actionSheet.addAction(cameraAction)
actionSheet.addAction(photoLibraryAction)
actionSheet.addAction(cancelAction)
mainViewController?.present(actionSheet, animated: true, completion: nil)
}
}
MainViewController is a class extending UIViewController which has the following button:
lazy private var sButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle('options', for: .normal)
button.backgroundColor = UIColor.yellow
button.roundedCorners(radius: 25)
button
.rx
.tap
.bind(to: self.viewModel.showOptions)
.disposed(by: self.disposableBag)
return button
}()
This is its init method
init(userDefaults: UserDefaults) {
viewModel = MainViewModel(userDefaults: userDefaults)
super.init(nibName: nil, bundle: nil)
}
The method self.viewModel.showOptions is of type PublishSubject, whenever I tap the sButton I want the showOptions method in MainCoordinator class to be activated.
import RxSwift
class MainViewModel {
let disposeBag = DisposeBag()
//Subjects
let showScanOptions = PublishSubject<Void>()
..
init(networkable: Networkable, userDefaults: UserDefaults) {
self.networkable = networkable
self.userDefaults = userDefaults
loginResponse.value = userDefaults.loginResponse
login.subscribe(onNext: { [weak self] in
guard let username = self?.email.value, let password = self?.password.value else { return }
self?.isLoading.onNext(true)
let loginInputs = LoginInputs(email: username, password: password)
self?.networkable.login(loginInputs: loginInputs) { loginResponse in
guard let loginResponse = loginResponse else {
self?.errorSubject.onNext(nil)
self?.isLoading.onNext(true)
return
}
self?.userDefaults.loginResponse = loginResponse
self?.loginResponse.value = loginResponse
}
}, onError: { [weak self] error in
self?.isLoading.onNext(false)
self?.errorSubject.onNext(error)
}).disposed(by: disposeBag)
}
}
How is this possible to achieve??
Any help is appreciated.

Related

Can't pass data between View Controller and Model

There is UIAlertController with text field in my View Controller. When user enter name of the city, this data must be transmitted to Model, when I get coordinates of this city. But I can't to pass name of the city from View Controller to Model
My UIAlertController:
class MainScrenenViewController: UIViewController {
var delegate: ILocationGroup?
#objc func locationButtonTap() {
let alert = UIAlertController(title: "Add city", message: nil, preferredStyle: .alert)
let addButton = UIAlertAction(title: "Add", style: .default) { action in
self.delegate?.addLocation(alert.textFields?.first?.text ?? "No City")
}
alert.addAction(addButton)
let cancelButton = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alert.addAction(cancelButton)
alert.addTextField { textField in
textField.placeholder = "Your City"
}
present(alert, animated: true, completion: nil)
}
My Model:
protocol ILocationGroup {
func addLocation(_ name: String)
}
class LocationGroup: ILocationGroup {
var mainScreenViewController: MainScrenenViewController?
func addLocation(_ name: String) {
mainScreenViewController?.delegate = self
let url = "https://geocode-maps.yandex.ru/1.x/?apikey=fd93783b-fe25-4428-8c3b-38b155941c8c&format=json&geocode=\(name)"
guard let url = URL(string: url) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
do {
let result = try JSONDecoder().decode(LocationData.self, from: data)
print(result.response.geoObjectCollection.metaDataProperty.geocoderResponseMetaData.boundedBy.envelope.lowerCorner)
}
catch {
print("failed to convert \(error)")
}
}
task.resume()
}
}
I think it is supposed to be var delegate: LocationGroup()
Also, I wouldn't be calling it delegate because registered delegate is a keyword in swift
https://manasaprema04.medium.com/different-ways-to-pass-data-between-viewcontrollers-views-8b7095e9b1bf

How can I create SignIn and SignOut with 1 button in RxSwift?

Here my code to change text in button:
func transform(input: Input) -> Output {
let tappedSigninCheck = input.signinTrigger
.scan(false) { lastState, _ in
return !lastState }
let singupButtonTitle = tappedSigninCheck.map {
return $0 == true ? "Sign Out" : "Sign In"
}
return Output(signinButtonTitle: singupButtonTitle)
}
Now when I clicked SignIn, button text will change to "Sign Out" and I want when click Sign Out, sign out will display 1 alert and when click Yes in alert then button text change to Sign In.
My problem: whenever I click in button then button text change text :(((
func bindViewModel() {
let signoutTrigger = signinButton.rx.tap.flatMap {
return Observable<Void>.create { (observer) -> Disposable in
let alert = UIAlertController(title: "Are you sure to sign out?",
message: nil,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No",
style: .cancel,
handler: nil))
alert.addAction(UIAlertAction(title: "Yes",
style: .destructive,
handler: { _ in
observer.onNext(Void())
}))
self.present(alert, animated: true)
return Disposables.create()
}
}
let input = ProfileViewModel.Input(signinTrigger: signinButton.rx.tap.asDriver(),
signoutTrigger: signoutTrigger.asDriver(onErrorJustReturn: Void()))
let output = profileViewModel.transform(input: input)
output
.signinButtonTitle
.drive(signinButton.rx.title)
.disposed(by: disposeBag)
}
For this you will need a feedback loop, and that calls for a Subject. I suggest you do it something like below. Also note that you forgot to emit a completed event on your alert.:
class ViewModel {
struct Input {
let logButton: Observable<Void>
let okayButton: Observable<Void>
}
struct Output {
let buttonTitle: Observable<String>
let displayAlert: Observable<Void>
}
private enum Action {
case tapped
case okay
}
private enum State {
case offline
case online
case check
}
func transform(_ input: Input) -> Output {
let state = Observable.merge(
input.logButton.map(to: ViewModel.Action.tapped),
input.okayButton.map(to: ViewModel.Action.okay)
)
.scan(ViewModel.State.offline) { state, action in
switch (state, action) {
case (.offline, .tapped):
return .online
case (.online, .tapped):
return .check
case (.check, .okay):
return .offline
case (.check, .tapped):
return .check
default:
assert(false)
return state
}
}
.share()
let buttonTitle = state
.map { $0 == .offline ? "Log In" : "Log Out" }
let displayAlert = state
.filter { $0 == .check }
.map(to: ())
return Output(
buttonTitle: buttonTitle,
displayAlert: displayAlert
)
}
}
class ViewController: UIViewController {
var button: UIButton!
var viewModoel: ViewModel!
let disposeBag = DisposeBag()
func bind() {
let logout = PublishSubject<Void>()
let input = ViewModel.Input(
logButton: button.rx.tap.asObservable(),
okayButton: logout
)
let output = viewModoel.transform(input)
output.buttonTitle
.bind(to: button.rx.title(for: .normal))
.disposed(by: disposeBag)
output.displayAlert
.flatMap { [unowned self] _ in
self.areYouSure()
}
.bind(to: logout)
.disposed(by: disposeBag)
}
func areYouSure() -> Observable<Void> {
Observable.create { [unowned self] observer in
let alert = UIAlertController(
title: "Are you sure to sign out?",
message: nil,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(
title: "Yes", style: .destructive, handler: { _ in
observer.onNext(())
observer.onCompleted()
}
))
alert.addAction(UIAlertAction(
title: "No", style: .default, handler: { _ in
observer.onCompleted()
}
))
self.present(alert, animated: true)
return Disposables.create()
}
}
}

How can I add alert button till my data count? How can I save my data choose from a action index?

I'm present a alert when I click the button. I choose from a list (if how much data is available.) How can I save my data choose from a list index?
You can see UI in here
My AccountServices
class AccountServices {
static let databaseReference = Database.database().reference(withPath: "Accounts").child((Auth.auth().currentUser?.uid)!)
static var account = Account()
static func saveChanges() {
databaseReference.setValue(try! FirebaseEncoder().encode(AccountServices.account))
}
static func getAccount() {
databaseReference.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { (snapshot, _) in
account = try! FirebaseDecoder().decode(Account.self, from: snapshot.value!)
})
}
}
Variable
var product: ProductViewModel?
addButton Tapped
#IBAction func addToCartButtonTapped(_ sender: UIButton) {
let alert = UIAlertController(title: "Bu ürünü hangi sepetinize eklemek istersiniz ?", message: "", preferredStyle: .actionSheet)
var indexer = 0
for cart in AccountServices.account.cart! {
if cart.product == nil{
AccountServices.account.cart![indexer].product = [Product]()
}
let action = UIAlertAction(title: cart.name , style: .default, handler: { (sender) in
if let index = alert.actions.firstIndex(where: { $0 === sender }) {
AccountServices.account.cart?[index].product?.append(self.product) `//Error: Cannot convert value of type 'ProductViewModel?' to expected argument type 'Product'`
AccountServices.saveChanges()//TODO...
}
let addAlert = UIAlertController(title: "Sepetinize Eklendi.", message: "Ürününüz sepetinize eklendi.", preferredStyle: .alert)
let okButton = UIAlertAction(title: "Tamam", style: .default, handler: nil)
addAlert.addAction(okButton)
self.present(addAlert, animated: true, completion: nil)
})
alert.addAction(action)
indexer += 1
}
let cancelaction = UIAlertAction(title: "Vazgeç", style: .cancel, handler: nil)
alert.addAction(cancelaction)
present(alert, animated: true, completion: nil)
}
}

use of unresolved identifier 'result' swift 3

I'm writing a login page for my app and i'm getting this error
social media app , xcode 8.3.2 , swift 3
i've tried target membership in file inspector and nothing changed
also I removed test units (UITest and Test) and renew them , it didn't worked either.
at the line 41 I'm getting this error "use of unresolved identifier 'result'"
the picture below explains the code
Picture
import UIKit
class LoginViewController : UIViewController
{
#IBOutlet weak var txt_username: UITextField!
#IBOutlet weak var txt_password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func btn_log_in_click(_ sender: Any){
let server=MultipartUtility (Url:"http://x.x.x.x/appname-api/user/login/")
//I.m hiding the real ip and details for posting this
server.AddFormField("username", value: txt_username.text)
server.AddFormField("password", value: txt_password.text)
let task = URLSession.shared.dataTask(with: server.execute())
{Data,URLResponse,error in
if error != nil{
print(error as Any)
return
}
do{
let json = try JSONSerialization.jsonObject(with: Data!, options: .allowFragments)
if let json_result = json as? [String: Any]{
let result = json_result ["result"] as? String
if result == "0"
{
DispatchQueue.main.async {
let alert = UIAlertController(title:"Incorrect Username",message : "The username you entered doesn't appear to belong to an account. Please check your username and try again", preferredStyle : .alert)
let alert_action = UIAlertAction(title: "Try Again", style: .default, handler: nil)
alert.addAction(alert_action)
self.present(alert, animated: true, completion: nil)
}
}
}
else{
DispatchQueue.main.async {
UserDefaults.standard.set(result!, forKey: "user_id")
//" use of unresolved identifier 'result' "
let current_view=UIApplication.shared.windows[0] as UIWindow
let new_view=(self.storyboard? .instantiateViewController(withIdentifier: "tab_bar"))! as UIViewController
UIView.transition(from: (current_view.rootViewController? .view)!, to:new_view.view , duration: 0.65, options: .transitionFlipFromRight, completion: {(action) in current_view.rootViewController=new_view
})
}
}
}
catch{
}
}
task.resume()
}
}
if let json_result = json as? [String: Any]
{
let result = json_result ["result"] as? String
if result == "0"
{
DispatchQueue.main.async {
let alert = UIAlertController(title:"Incorrect Username",message : "The username you entered doesn't appear to belong to an account. Please check your username and try again", preferredStyle : .alert)
let alert_action = UIAlertAction(title: "Try Again", style: .default, handler: nil)
alert.addAction(alert_action)
self.present(alert, animated: true, completion: nil)
}
}
else
{
DispatchQueue.main.async {
UserDefaults.standard.set(result!, forKey: "user_id")
//" use of unresolved identifier 'result' "
let current_view=UIApplication.shared.windows[0] as UIWindow
let new_view=(self.storyboard? .instantiateViewController(withIdentifier: "tab_bar"))! as UIViewController
UIView.transition(from: (current_view.rootViewController? .view)!, to:new_view.view , duration: 0.65, options: .transitionFlipFromRight, completion: {(action) in current_view.rootViewController=new_view
})
}
}
}
else{
// Error in jsonSerialization
}

cancel button using MBProgressView

I am trying to use cancel button with the MBProgressView. I am getting the error "cannot convert value of type '()' to expected argument type 'Selector'"
hud.button.addTarget(hud.progressObject, action: cancelButton(), for: .touchUpInside)
I have also tried doing this:
hud.button.addTarget(hud.progressObject, action: #selector(cancelButton), for: .touchUpInside)
and I got the error "Argument of #selector cannot refer to local function 'cancelButton()'".
Can anyone explain to me what am i doing wrong?
cancelButton should be in viewDidLoad or at least I need to find a way to access what's inside viewDidload, because I need to use hud and snapshot.progress to cancel the download:
override func viewDidLoad() {
super.viewDidLoad()
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.orintation = UIInterfaceOrientationMask.allButUpsideDown
if book?.bookPath != book?.bookPath {
print("HERE \(book?.bookPath)")
loadReader(filePaht: (book?.bookPath)!)
} else {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let strName = book?.id
let filePath = "\(documentsPath)/"+strName!+".pdf"
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
loadReader(filePaht: filePath)
return;
}
print("DOWNLOAD #1")
let reference = FIRStorage.storage().reference(forURL: (self.book?.bookURL)!)
let downloadTask = reference.data(withMaxSize: 50 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
} else {
if ((try! data?.write(to: URL.init(fileURLWithPath: filePath, isDirectory: false))) != nil) {
self.db.upDate(id: (self.book?.id)!, bookPath: filePath)
self.loadReader(filePaht: filePath)
}
}
}
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in
DispatchQueue.global(qos: .default).async(execute: {() -> Void in
self.showHUDWithCancel("Downloading")
DispatchQueue.main.async(execute: {() -> Void in
})
})
self.hud.progressObject = snapshot.progress
}
downloadTask.observe(.success) { (snapshot) -> Void in
// Download completed successfully
print("Download Success")
SwiftLoader.hide()
}
downloadTask.observe(.failure) { (snapshot) -> Void in
//Download failed
print("Download failed")
}
}
}
func showHUDWithCancel(_ aMessage: String) {
self.hud = MBProgressHUD.showAdded(to: self.view, animated: true)
self.hud.mode = MBProgressHUDMode.annularDeterminate
self.hud.label.text = aMessage
self.hud.detailsLabel.text = "Tap to cancel"
let tap = UITapGestureRecognizer(target: self, action: #selector(cancelButton))
self.hud.addGestureRecognizer(tap)
}
func cancelButton() {
self.hud.hide(animated: true)
self.hud.progressObject?.cancel()
print("cancel button is working")
}
This is the Cancel Button function
func cancelButton() {
MBProgressHUD.hide(for: view, animated: true)
snapshot.progress?.pause()
}
Try this -
Call below showHUDWithCancel from where you want to add hud with Cancel.
class ViewController: UIViewController {
var hud = MBProgressHUD()
override func viewDidLoad() {
super.viewDidLoad()
}
func showHUDWithCancel(_ aMessage: String) {
self.hud = MBProgressHUD.showAdded(to: self.view, animated: true)
self.hud.label.text = aMessage
self.hud.detailsLabel.text = "Tap to cancel"
let tap = UITapGestureRecognizer(target: self, action: #selector(cancelButton))
self.hud.addGestureRecognizer(tap)
}
func cancelButton() {
self.hud.hide(animated: true)
// do your other stuff here.
}
}
Add this code within your viewDidLoad it will work.
override func viewDidLoad() {
super.viewDidLoad()
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.orintation = UIInterfaceOrientationMask.allButUpsideDown
if book?.bookPath != book?.bookPath {
print("HERE \(book?.bookPath)")
loadReader(filePaht: (book?.bookPath)!)
} else {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let strName = book?.id
let filePath = "\(documentsPath)/"+strName!+".pdf"
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
loadReader(filePaht: filePath)
return;
}
print("DOWNLOAD #1")
let reference = FIRStorage.storage().reference(forURL: (self.book?.bookURL)!)
downloadTask = reference.data(withMaxSize: 50 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
} else {
if ((try! data?.write(to: URL.init(fileURLWithPath: filePath, isDirectory: false))) != nil) {
self.db.upDate(id: (self.book?.id)!, bookPath: filePath)
self.loadReader(filePaht: filePath)
}
}
}
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in OperationQueue.main.addOperation {
OperationQueue.main.addOperation {
self.hud.progressObject = snapshot.progress
self.showHUDWithCancel("Downloading")
}
}
}
downloadTask.observe(.success) { (snapshot) -> Void in OperationQueue.main.addOperation {
// Download completed successfully
print("Download Success")
OperationQueue.main.addOperation {
SwiftLoader.hide()
}
}
}
downloadTask.observe(.failure) { (snapshot) -> Void in OperationQueue.main.addOperation {
//Download failed
print("Download failed")
OperationQueue.main.addOperation {
_ = self.navigationController?.popViewController(animated: false)
}
}
}
}
}
Move definition of downloadTask outside of the viewDidLoad method scope into the class itself. This way you'll be able to access task directly, not via snapshot passed in observers, or progress attached to either downloadTask or progressHUD. Doing so you could access task from any method of your view controller including cancelButton():
task.pause()
instead of
snapshot.progress?.pause()
Final code could look like:
class ViewController: UIViewController {
var downloadTask: FIRStorageDownloadTask!
...
override func viewDidLoad() {
super.viewDidLoad()
...
let reference = FIRStorage.storage().reference(forURL: (self.book?.bookURL)!)
downloadTask = reference...
...
}
}
NOTICE: For those of you who use the latest version of MBProgressView, the button documentation has been changed:
/**
* A button that is placed below the labels. Visible only if a target / action is added and a title is assigned..
*/
So, the creation should look something like the following:
class Tools {
static func popLoadingDialog(viewParent: UIView,
label: String,
cancelTarget: Any? = nil,
cancelSelector: Selector? = nil) -> MBProgressHUD {
let loadingNotification = MBProgressHUD.showAdded(to: viewParent, animated: true)
loadingNotification.mode = MBProgressHUDMode.indeterminate
loadingNotification.label.text = label
if(cancelSelector != nil) {
loadingNotification.button.setTitle("Cancel", for: .normal)
loadingNotification.button.addTarget(cancelTarget, action: cancelSelector!, for: .touchUpInside)
}
return loadingNotification
}
}
and call it:
loadingIndicator = Tools.createLoadingDialog(viewParent: view,
label: "Please wait...",
cancelTarget: self,
cancelSelector: #selector(onCancelClick))
loadingIndicator?.show(animated: true)
}
#objc func onCancelClick(){
// do something when the user click on cancel...
}