Observing ViewWillAppear (Migrating RXSwift to combine) - ios

extension Reactive where Base: UIViewController {
var viewWillAppear: Observable<Void> {
sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.filter { [weak base] _ in
if base?.isImageViewerPresented == true {
return false
}
return true
}
.mapToVoid()
}
}
i want observe viewWillAppear like self.viewWillApplerPublisher
please help how to observe it

extension Reactive where Base: UIViewController {
public var viewWillAppear: ControlEvent<Void> {
let source = base.rx.methodInvoked(#selector(Base.viewWillAppear))
.map { _ in }
return ControlEvent(events: source)
}
}

Related

swift set crash in asynchous queue

I found the weird crash when using Set in async queue closure below, confirmed that it only happens in async queue, but Array works.
func testA1() {
var set = Set<Int>()
for i in 0...10 {
DispatchQueue.global().async {
set.update(with: i) // Crash here: EXC_BAD_ACCESS
// set.insert(i)
}
}
print(set as Any)
}
func testA2() {
var set = Set<Int>()
for i in 0...10 {
DispatchQueue.global().sync {
set.update(with: i) // Works!
}
}
print(set as Any)
}
func testB() {
var array = [Int]() // Works!
for i in 0...10 {
DispatchQueue.global().async {
array.append(i)
}
}
print(array as Any)
}
Swift version:
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin20.2.0
My bad or swift bug? Why?
Most of datatypes in Swift aren't thread-safe.
You should interact using different ways.
protocol ThreadSafeExecutor {
var semaphore: DispatchSemaphore { get set }
func wait()
func signal()
}
extension ThreadSafeExecutor {
func wait() {
semaphore.wait()
}
func signal() {
semaphore.signal()
}
}
class YourClass: ThreadSafeExecutor {
func someMethod() {
wait()
defer {
signal()
}
/// thread-safe code
}
}

UINavigationController return nil Swift

When i try print(self.navigationController) in viewWillApear or viewDidLoad all ok. But when delegate return response from API print(self.navigationController) return nil. What could it be?
extension EnterpriseList: APIDataDelegate {
func successRequest() { //print(self.navigtionController) == nil
DispatchQueue.main.async {
self.navigationController?.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
Alert.showWarningAlert(withTitle: "Внимание!", andMessage: "Ошибка получения данных, попробуйте чуть позже", whereSender: self)
}
}
}
class EnterpriseList: UIViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
dataSource.makeAPICall()
}
}
extension EnterpriseList: APIDataDelegate {
func successRequest() {
DispatchQueue.main.async {
self.navigationController!.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
}
}
}
protocol APIDataDelegate: class {
func successRequest()
func badRequest()
}
class DataSource {
var delegate: APIDataDelegate?
private let queue = DispatchQueue(label: "Test", qos: .background, attributes: [], autoreleaseFrequency: .inherit, target: nil)
func makeAPICall() {
queue.asyncAfter(deadline: .now() + 2) {[weak self] in
self?.delegate?.successRequest()
}
queue.asyncAfter(deadline: .now() + 10) {[weak self] in
self?.delegate?.successRequest()
}
}
}
The crash is because you are calling the method on deallocated object.
Here is how to fix this:
Make your delegate is weak and you are correctly deallocating the objects
weak var delegate: APIDataDelegate?
Thx #Manoj for answers it's halped me. But i found solution in other. Besides SurveyList i have UserMenu where i create static let view controllers. Removing static i solved my problem.

Proper way of setting delegates in MVVM

I would like to know what is a proper way of setting delegates in the ViewModel in MVVM pattern in Swift.
I'm instantiating the ViewController from another class:
let viewModel = DashboardViewModel()
let viewController = DashboardViewController(viewModel: viewModel)
My ViewModel:
protocol DashboardViewModelType {
var items: [Item] { get }
var reloadDelegate: DashboardDataReloadDelegate? { get set }
}
protocol DashboardDataReloadDelegate: class {
func reloadData()
}
class DashboardViewModel: DashboardViewModelType {
var items: [Item] = []
weak var reloadDelegate: DashboardDataReloadDelegate?
init() {
loadItems()
}
func loadItems() {
let databaseFetcher = DatabaseDaysFetcher()
databaseFetcher.getDays(onData: { (items) in
self.items = items
reloadDelegate?.reloadData() //delegate is nil here
}) { (error) in
print(error)
}
}
}
and ViewController:
class DashboardViewController: UIViewController {
var viewModel: DashboardViewModelType?
init(viewModel: DashboardViewModelType) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.viewModel!.reloadDelegate = self // it is executed after
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension DashboardViewController: DashboardDataReloadDelegate {
func reloadData() {
print("data reloaded")
}
}
So the main problem is that if I want to inject the viewModel in another class I'm instantiating the viewModel when delegate is not yet set. Would it be better to declare loadItems inside the DashboardViewModelType protocol and then call this function from the init or viewDidLoad inside the ViewController?
Yes, you could inject DatabaseDaysFetcher in the init for the DashboardViewModel and then as you say, move loadItems to the DashboardViewModelType protocol.
Then when you call loadItems, it should callback in to the caller.
Then use [weak self] in the loadItems callback.
This would negate the need for the delegate.
protocol DashboardViewModelType {
init(databaseFetcher: DatabaseDaysFetcher)
func loadItems(completion: ([Item]) -> Void, error: (Error) -> Void)
}
final class DashboardViewModel: DashboardViewModelType {
private var databaseFetcher: DatabaseDaysFetcher
init(databaseFetcher: DatabaseDaysFetcher) {
self.databaseFetcher = databaseFetcher
}
func loadItems(completion: ([Item]) -> Void, onError: (Error) -> Void) {
self.databaseFetcher.getDays(onData: { (items) in
completion(items)
}) { (error) in
onError(error)
}
}
}

Swift closure in protocol extension

I want to Decorate UIViewController with the ability to adjust it's interface when setInteractionEnabled method is called from another class (ex. Network State Manager). All changes (if any) should be provided in the concrete controller by overriding onInteractionChanged. Here is my code:
import Foundation
typealias InteractionClosure = ((enabled: Bool) -> Void)
protocol Interaction: class {
var onInteractionChanged: InteractionClosure? { get set }
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
// Default: Do nothing
// Throws: - Extensions may not contain stored properties
var onInteractionChanged: InteractionClosure? = nil
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
}
extension UIViewController : Interaction {}
How to add default implementation for onInteractionChanged?
Answering my own question is something usually I don't do, but here is my solution:
typealias InteractionClosure = (enabled: Bool) -> Void
protocol Interaction: class {
func addOnInteractionChanged(closure: InteractionClosure)
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
func addOnInteractionChanged(closure: InteractionClosure) {
onInteractionChanged = closure
}
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
// MARK: - Private
private var onInteractionChanged: InteractionClosure? {
get {
let wrapper =
objc_getAssociatedObject(self, &icAssociationKey) as? ClosureWrapper
return wrapper?.closure
}
set(newValue) {
objc_setAssociatedObject(self,
&icAssociationKey,
ClosureWrapper(newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
}
extension UIViewController : Interaction {}
// Helpers
private var icAssociationKey: UInt8 = 0
private class ClosureWrapper {
var closure: InteractionClosure?
init(_ closure: InteractionClosure?) {
self.closure = closure
}
}
Client class:
class LoginViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.setup()
}
// MARK: - Private
private func setup() {
// ...
addOnInteractionChanged { [unowned self] (enabled) in
self.signInButton.enabled = enabled
self.activityIndicatorView.hidden = !enabled
}
}
}
In manager class:
visibleViewController?.setInteractionEnabled(true)
If you would like property to have only { get } ability, you can use:
protocol TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? { get }
}
extension TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? {
return { parameter in
print(parameter)
}
}
}

transitionCoordinator return nil for custom container view controller in iOS8

when reference transitionCoordinator on child vc, it used to called transitionCoordinator on custom container class in iOS7, but in iOS8 this wasn't the case. Now it return nil and I have no clue what should I change to make this work.
I guess its about UIPresentationController introduced in iOS8, but can't find proper implementation for custom container view controller.
As Matt said in this previous SO question:
So, since you can't even get a transition coordinator in a situation
where you are allowed to write a custom transition animation for a
built-in parent view controller, obviously the chances of your getting
one in a situation where you're trying to do your own parent view
controller are zero
But, according to transitionCoordinator, overriding it is allowed:
Container view controllers can override this method but in most cases
should not need to. If you do override this method, first call super
to see if there is an appropriate transition coordinator to return,
and, if there is, return it.
So, I would try to create my own coordinator for my own VC container. If you use a UIViewPropertyAnimator to manipulate the VC container's children, it's almost straightforward.
Here is an example:
class PropertyAnimatorTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator, UIViewControllerTransitionCoordinatorContext {
private let parentView: UIView
private let fromViewController: UIViewController?
private let toViewController: UIViewController
private let animator: UIViewPropertyAnimator
// MARK: - Life Cycle
init(parentView: UIView,
fromViewController: UIViewController?,
toViewController: UIViewController,
animator: UIViewPropertyAnimator) {
self.parentView = parentView
self.fromViewController = fromViewController
self.toViewController = toViewController
self.animator = animator
}
// MARK: - UIViewControllerTransitionCoordinator
func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?,
completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool {
var isSuccessful = false
if let animation = animation {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
animation(context)
}
isSuccessful = true
}
if let completion = completion {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
completion(context)
}
isSuccessful = true
}
return isSuccessful
}
func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool {
return animate(alongsideTransition: animation, completion: completion)
}
func notifyWhenInteractionEnds(_ handler: #escaping (UIViewControllerTransitionCoordinatorContext) -> Void) {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
handler(context)
}
}
func notifyWhenInteractionChanges(_ handler: #escaping (UIViewControllerTransitionCoordinatorContext) -> Void) {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
handler(context)
}
}
// MARK: - UIViewControllerTransitionCoordinatorContext
var isAnimated: Bool {
return true
}
var presentationStyle: UIModalPresentationStyle {
return .none
}
var initiallyInteractive: Bool {
return false
}
var isInterruptible: Bool {
return animator.isInterruptible
}
var isInteractive: Bool {
return animator.isUserInteractionEnabled
}
var isCancelled: Bool {
return !animator.isRunning
}
var transitionDuration: TimeInterval {
return animator.duration
}
var percentComplete: CGFloat {
return animator.fractionComplete
}
var completionVelocity: CGFloat {
return 0
}
var completionCurve: UIView.AnimationCurve {
return animator.timingParameters?.cubicTimingParameters?.animationCurve ?? .linear
}
var targetTransform: CGAffineTransform {
return .identity
}
var containerView: UIView {
return parentView
}
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController? {
switch key {
case .from:
return fromViewController
case .to:
return toViewController
default:
return nil
}
}
func view(forKey key: UITransitionContextViewKey) -> UIView? {
switch key {
case .from:
return fromViewController?.view
case .to:
return toViewController.view
default:
return nil
}
}
}
In my custom container, I would use it like this:
class CustomContainerViewController: UIViewController {
private var customTransitionCoordinator: UIViewControllerTransitionCoordinator?
override var transitionCoordinator: UIViewControllerTransitionCoordinator? {
if let coordinator = super.transitionCoordinator {
return coordinator
}
return customTransitionCoordinator
}
override var shouldAutomaticallyForwardAppearanceMethods: Bool {
return false
}
func insertNewChild(_ viewController: UIViewController) {
let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut)
customTransitionCoordinator = PropertyAnimatorTransitionCoordinator(
parentView: view,
fromViewController: nil,
toViewController: viewController,
animator: animator
)
animator.addCompletion { [weak self] _ in
guard let parent = self else { return }
viewController.didMove(toParent: parent)
self?.customTransitionCoordinator = nil
}
addChild(viewController)
viewController.beginAppearanceTransition(true, animated: true)
view.addSubview(viewController.view)
let target = view.bounds
viewController.view.frame = target
viewController.view.frame.origin.x = -target.width
view.layoutIfNeeded()
animator.addAnimations {
viewController.view.frame = target
}
animator.addCompletion { [weak self] _ in
guard let parent = self else { return }
viewController.endAppearanceTransition()
viewController.didMove(toParent: parent)
self?.customTransitionCoordinator = nil
}
animator.startAnimation()
}
}
Of course, some edge cases are not handled. It's a really basic example.

Resources