Last active
May 19, 2021 00:11
-
-
Save steipete/01e9544b1d86d7fc5cba9f85bd72bce0 to your computer and use it in GitHub Desktop.
How to build Tap-And-Fade using SwiftUI instead of a hard deselect. The default in SwiftUI is too hard and doesn't feel right.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Helper to hold the parent VC weakly | |
class WeakViewControllerHolder: ObservableObject { | |
weak var vc: UIViewController? | |
init(_ vc: UIViewController) { | |
self.vc = vc | |
} | |
} | |
@available(iOS 13.0, *) | |
struct FadableButton: View { | |
@EnvironmentObject var sender: WeakViewControllerHolder | |
let title: String | |
let action: () -> Void | |
init(_ title: String, action: @escaping () -> Void) { | |
self.title = title | |
self.action = action | |
} | |
var body: some View { | |
Button(title) { | |
sender.vc?.deselectSelectedRow() | |
action() | |
} | |
} | |
} | |
// The settings view where we want the nice fade | |
struct SettingsView: View { | |
@EnvironmentObject var sender: WeakViewControllerHolder | |
// later in your List/Form. Could also be wrapped! | |
FadableButton("Some Action") { | |
// do your action | |
} | |
} | |
// Settings is embedded in a regular view controller and created like this: | |
func makeSettingsView() -> UIHostingController<AnyView> { | |
UIHostingController(rootView: SettingsView().environmentObject(WeakViewControllerHolder(self)).eraseToAnyView()) | |
} | |
extension UIViewController { | |
/// Helper to deselect the selected cell row. | |
func deselectSelectedRow(animated: Bool = true) { | |
// This helper is used for UITableViewController subclasses but also generic UIViewController or SwiftUI holding ones, so we search. | |
guard let tableView = view.closestSubview(of: UITableView.self), | |
let selectedIndex = tableView.indexPathForSelectedRow else { return } | |
tableView.deselectRow(at: selectedIndex, animated: animated) | |
} | |
} |
Apple seems to like parent-child controller hierarchies. In my SwiftUI wrapper, I used a UIHostingController to spy on child controllers that are added to the hosting controller. You can use that to discover the parent table view controller.
Since you have other issues with the table view, it’s probably a good idea to wrap that one, take over it’s delegate with a proxy object (or ISA swizzle the original 🤪) and enhance the returned values and actions.
Folks commenting that making a Button subclass is not SwiftUI-y: I tried Configuration and ViewModifiers with gesture recognizers, but it's all cursed, fires too late and/or delays the action call on button. Hours wasted: +=2
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Questions:
Tweet/Video: https://twitter.com/steipete/status/1353296114808737792?s=21