So I have a FeatureTutorialDelegate protocol with one buttonPressed() method. I have a FeatureTutorialViewController that functions as the delegate. A PageViewController calls these FeatureTutorialViewController classes. The different pages have multiple buttons.
How do I make the different buttons do different things with the buttonPressed() method? I need the button to dismiss the tutorial, open other ViewControllers, etc, depending on which tutorial page the user taps the button on.
Create an enum for the actions.
enum Action {
case .dismiss
case .open
}
protocol ADelegate {
func buttonPressed(action: Action)
}
class A(){
func navigate(){
let b = B()
b.delegate = self
}
}
extension A: ADelegate {
func buttonPressed(action: Action) {
if action == .dismiss {
} else if action == .open{
}
}
}
class B {
var delegate: ADelegate?
func dismiss(){
self.delegate?.buttonPressed(action: .dismiss)
}
func open(){
self.delegate?.buttonPressed(action: .dismiss)
}
}
Read this thread should you need to know why you should not use string as the other answer suggests.
What are enums and why are they useful?
You may define multiple methods in the protocol. Doing so you avoid using if-else statement. It is cleaner.
protocol ADelegate {
func dismiss()
func open()
func navigate()
}
you need to pass variable in delegate method so when delegate is call check that variable and perform action accordingly.
protocol MoveFeatureTutorial {
func buttonPressed(check: String)
}
var delegate: MoveFeatureTutorial
put below code in button action method
self.delegate?.buttonPressed(check: "For Button A")
self.delegate?.buttonPressed(check: "For Button B")
It's is the method that that protocol
func buttonPressed(check: String?) {
if check == "For Button A" {
print(check)
} else if check == "For Button B" {
print(check)
} else {
print(check)
}
}
Related
I am starting on combine with swift, but having some hard time (had experience working in swiftui before).
So the question is how to perform certain operation:
lets say i have vc1. and I go to vc2 from there
2.then i start asynchronos network closure and come back to vc1 (by popping out vc2).
Now say i want to ge just a string from vc2's asycnrhoss clousre to vc1 when i am back to vc1.
how can i achieve this?
I want to use publisher of lets say <String, Never>
how can I subscribe in my vc1 and publish or send it from vc2 ?
I am using this approach but its not working, it never comes to code under sink.....
public class Parent {
public static let shared = Parent()
public var publisher = PassthroughSibject<String,Never>()
}
class vc1: ViewController {
func viewdidLoad() {
let subscription = Parent.shared.oublisehr.sink { (result) in
print(result)
}
}
func navigatetoVC1() {
///// some code to navigate to vc1
}
func button() {
self.navigatetoVC1
}
}
class vc2: ViewController {
func viewDidload() {
///
}
func performsomeOperation() {
someasyncoperation(completion: { result in
switch result {
case .success:
//send some data to vc1
Parent.shared.publisher.send("testdata")
case .failure:
//send some data to vc1
})
self.dismisVC2() //some method to pop out vc2
}
}
Your code is almost right, except you are using your Anycancellable inside viewdidload, so its scope is getting exhausted. So use it outside in the view controller as an optional AnyCancellable type.
Below code should work.
class vc1: ViewController {
var subscription = AnyCancellable?
func viewdidLoad() {
self.subscription = Parent.shared.oublisehr.sink { (result) in
print(result)
}
}
func navigatetoVC1() {
///// some code to navigate to vc1
}
func button() {
self.navigatetoVC1
}
}
I am trying to pass data receive from a network call to another view controller when user has clicked on a button. When making printing on the FirstVC, data is in, but when printing the result in the SecondVC, there is no more value. I don' t want to use delegate but closure instead.
Also, when trying to retain the memory cycle, an error appear...
class APIsRuler {
static var oneRecipeFound: ((OneRecipeSearch) -> ())?
}
class FirstVC: UIViewController {
func cellIsClicked(index: Int) {
APIsRuler.shared.getRecipe(from: recipeID) { (success, oneRecipe) in
if success, let oneRecipe = oneRecipe {
APIsRuler.oneRecipeFound?(oneRecipe)
self.performSegue(withIdentifier: "goToSecondVC", sender: self)
}
}
}
}
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in
print(result)
}
}
}
Doing this in SecondVC
APIsRuler.oneRecipeFound = { result in
print(result)
}
and this in first
APIsRuler.oneRecipeFound?(oneRecipe)
have no inner communications , you need to read your data directly from the shared class in the secondVc after the segue or send it in
self.performSegue(withIdentifier: "goToSecondVC", sender: <#Herererere#>)
and implement prepareForSegue
Let’s think about the order in which things are happening:
class APIsRuler {
static var oneRecipeFound: ((OneRecipeSearch) -> ())? // 1
}
class FirstVC: UIViewController {
func cellIsClicked(index: Int) {
APIsRuler.shared.getRecipe(from: recipeID) { (success, oneRecipe) in
if success, let oneRecipe = oneRecipe {
APIsRuler.oneRecipeFound?(oneRecipe) // 2
self.performSegue(withIdentifier: "goToSecondVC", sender: self)
}
}
}
}
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in // 3
print(result)
}
}
}
oneRecipeFound starts out life empty: it is nil.
In FirstVC, the cell is clicked. We call oneRecipeFound. It is still nil, so nothing happens.
In SecondVC, we set the value of oneRecipeFound. Now it has a value, but the call has already happened.
So unless you have a time machine in your pocket, so that you can reverse that order of events somehow, the strategy you’ve outlined is doomed to failure. Of course, if you call oneRecipeFound after setting it, it will work. For example:
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in
print(result)
}
APIsRuler.oneRecipeFound?(oneRecipe) // prints
}
}
I am working on two views that are subclassing subclass of UITableViewCell. In the base one (subclass of UITableViewCell) I am trying to setup gesture recognizer in a way that each of super class could change the behavior (eventually call didTapped method on it's delegate) of the tap.
I have written following code. I can use #selector(tap), however I think that using a variable instead of overriding a tap method in each super class is a much cleaner way. Is it even possible to use something like #selector(tapFunc)? If no what would be the cleanest and best from engineering point of view solution?
class BaseCell: UITableViewCell {
#objc var tapFunc: () -> () = { () in
print("Tapped")
}
#objc func tap() {
print("TEST")
}
func setupBasicViews(withContent: () -> ()) {
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(tapFunc))
contentView.isUserInteractionEnabled = true
contentView.addGestureRecognizer(tapGestureRecoginzer)
}
}
And then two views that are building on top of this one:
class ViewA: BaseCell {
//don't want to do this
override func tap() {
//do stuff
}
func setup {
//setup everything else
}
class ViewB: BaseCell {
var delegate: ViewBProtocool?
func setup {
tapFunc = { () in
delegate?.didTapped(self)
}
//setup everything else
}
You're not too far off. Make the following changes:
class BaseCell: UITableViewCell {
var tapFunc: (() -> Void)? = nil
// Called by tap gesture
#objc func tap() {
tapFunc?()
}
func setupBasicViews(withContent: () -> ()) {
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(tap))
contentView.isUserInteractionEnabled = true
contentView.addGestureRecognizer(tapGestureRecoginzer)
}
}
class ViewA: BaseCell {
func setup() {
//setup everything else
}
}
class ViewB: BaseCell {
var delegate: ViewBProtocol?
func setup() {
tapFunc = {
delegate?.didTapped(self)
}
//setup everything else
}
}
Now each subclass can optionally provide a closure for the tapFunc property.
I show above that tapFunc is optional with no default functionality in the base class. Feel free to change that to provide some default functionality if desired.
I am creating a custom subclass of UIControl (I need to override its draw method) and I want to add RxSwift to bind its isSelected property to my model.
So far so good. This works fine.
My problem is how can I do to change the value isSelected property in response of user touchUpInside event?.
My first try was to use the addTarget method of UIControl, but changing the value of isSelected programmatically is not reported by the ControlProperty (as stated in the doc). But I can figure another way to resolve this.
Any help appreciated.
Source code of the subclass:
class SYYesNoButton: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
// subscribe to touchUpInside event
addTarget(
self,
action: #selector(userDidTouchUpInside),
for: UIControlEvents.touchUpInside)
}
func userDidTouchUpInside() {
// change the value of the property
// this does not work,
// the change is not reported to the ControlProperty
// HOW CAN I CHANGE THIS ??
self.isSelected = !isSelected
}
}
Extensions to add reactive support:
extension SYYesNoButton {
var rx_isSelected: ControlProperty<Bool> {
return UIControl.valuePublic(
self,
getter: { (button) -> Bool in
return button.isSelected
},
setter: { (button, value) in
button.isSelected = value
})
}
}
extension UIControl {
static func valuePublic<T, ControlType: UIControl>(_ control: ControlType, getter: #escaping (ControlType) -> T, setter: #escaping (ControlType, T) -> ()) -> ControlProperty<T> {
let values: Observable<T> = Observable.deferred { [weak control] in
guard let existingSelf = control else {
return Observable.empty()
}
return (existingSelf as UIControl).rx.controlEvent([.allEditingEvents, .valueChanged])
.flatMap { _ in
return control.map { Observable.just(getter($0)) } ?? Observable.empty()
}
.startWith(getter(existingSelf))
}
return ControlProperty(values: values, valueSink: UIBindingObserver(UIElement: control) { control, value in
setter(control, value)
})
}
}
Thanks for all.
Once you have an actual UIControl, there's an even nicer way to a "native" RxCocoa extension called a ControlProperty using a helper method in RxCocoa.
For example:
extension Reactive where Base: someControl {
var someProperty: ControlProperty<Float> {
return controlProperty(editingEvents: .valueChanged,
getter: { $0.value },
setter: { $0.value = $1 })
}
}
This will expose the current value from the getter block whenever the specified UIControlEvent is fired, and will also set the value whenever some stream is bound to it.
It sort of acts like an Observable and Observer type together - you can observe its value, but can also subscribe to it.
If you are subclassing from UIControl, then you are making your own control class and you have to override one or more of beginTracking(_:with:), continueTracking(_:with:), endTracking(_:with:), or cancelTracking(with:) to make the control work the way you want. Then call sendActions(for:) with the correct event. The guts of a UIControl would not have Rx in it.
Taking a queue from UIButton, your control should not select itself, although it can highlight and unhighlight itself (when the user's finger is on it for example.)
Once you have properly created your UIControl, code outside the control can use Rx to observe it with no extra work on your part.
The following works (Updated for Swift 5/RxSwift 5):
class ViewController: UIViewController {
#IBOutlet weak var yesNoButton: SYYesNoButton!
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
yesNoButton.rx.controlEvent(.touchUpInside)
.scan(false) { v, _ in !v }
.bind(to: yesNoButton.rx.isSelected)
.disposed(by: bag)
}
}
#IBDesignable
class SYYesNoButton: UIControl {
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = isSelected ? .green : .red
}
override var isSelected: Bool {
didSet {
super.isSelected = isSelected
backgroundColor = isSelected ? .green : .red
}
}
}
I have these two IBActions in WorkoutsController.swift.
#IBAction func startWalkingButton() {
print("Walking start button pressed")
presentControllerWithName("Dashboard", context: sessionContext)
wSM!.startWorkout()
}
#IBAction func startCyclingButton() {
print("Cycling start button pressed")
presentControllerWithName("Dashboard", context: sessionContext)
wSM!.startWorkout()
}
They are calling the startWorkout() function in WorkoutSessionManager.swift
func startWorkout() {
self.healthStore.startWorkoutSession(self.workoutSession)
if ... {
print("startWorkout() called from startWalkingButton")
} else if ... {
print("startWorkout() called from startCyclingButton")
}
}
How do I create a condition to print out different print statements depending on which button function called the method? Should I use an if statement or switch statement?
I know there is already a print statement for the separate IBActions but I want to know if it's possible to do it in the reverse for redundancy.
Simply add one Bool parameter with your method startWorkout
func startWorkout(isFromWalking: Bool) {
if (isFromWalking) {
print("startWorkout() called from startWalkingButton")
}
else {
print("startWorkout() called from startCyclingButton")
}
}
Now call this function from startWalkingButton method with passing true
startWorkout(true)
and from startCyclingButton method with passing false
startWorkout(false)
Edit:
You haven't told that you have multiple option, then best option is to used enum in this case, create one enum like this and use that with the method
enum Workout {
case Walking
case Cycling
//Add case that you have
}
Now change the function like this
func startWorkout(workout: Workout) {
switch(workout) {
case .Walking :
print("Walking")
case .Cycling:
print("Cycling")
}
}
And call the function like this
self.startWorkout(.Walking)
self.startWorkout(.Cycling)
Simply add some sort of 'sender' parameter to your startWorkout() method.
Example:
// Hold a reference to your buttons, connected from IB
#IBOutlet var startWalkingButton: UIButton!
#IBOutlet var startCyclingButton: UIButton!
// here are your .TouchUpInside actions
// UIControl action methods receive the sender of the event as the first parameter (sender)
#IBAction func startWalkingButtonTouched(sender: AnyObject) {
...
startWorkout(sender)
}
#IBAction func startCyclingButtonTouched(sender: AnyObject) {
...
startWorkout(sender)
}
func startWorkout(sender: AnyObject) {
self.healthStore.startWorkoutSession(self.workoutSession)
switch sender {
case startWalkingButton:
print("startWorkout() called from startWalkingButton")
break
case startCyclingButton:
print("startWorkout() called from startCyclingButton")
break
default: ()
}
}
Hope this helps.
I feel you should probably use a block here. startWorkout method should accept an optional block. This approach avoids passing arguments and also avoids having if and case statements.
class Walking {
let workout = Workout()
func startWalkingButton() {
print("startWalkingButton")
workout.startWorkout() {
print("Walking Over")
}
}
}
class Cycling {
let workout = Workout()
func startCyclingButton() {
print("startCyclingButton")
workout.startWorkout() {
print("Cycling Over")
}
}
}
class Workout {
func startWorkout( afterWorkout: () -> Void ){
print("startWorkout")
afterWorkout()
}
}
let w = Walking()
w.startWalkingButton()