Skip to content

Commit

Permalink
fix: handle weird unresponsiveness on FavoriteView
Browse files Browse the repository at this point in the history
  • Loading branch information
uwaisalqadri committed Feb 1, 2025
1 parent e3c00a9 commit 82feba9
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 81 deletions.
75 changes: 48 additions & 27 deletions Giffy/App/Router/Routing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,67 +19,69 @@ public final class Routing<T: RouterIdentifiable> {
public var onPresent: ((T, Bool) -> Void)?
public var onPopLast: ((Int, Bool) -> Void)?
public var onPopToRoot: ((Int?, Bool) -> Void)?

public var currentRoutes: [T] {
return routes
}

public init(initial: T? = nil) {
if let initial = initial { push(initial) }
}

public func makeRoot(_ route: T, animated: Bool = true, needValidate: Bool = false) {
guard !(needValidate && routes.last?.key == route.key) else { return }
routes = [route]
onMakeRoot?(route, animated)
}

public func push(_ route: T, animated: Bool = true, needValidate: Bool = false) {
guard !(needValidate && routes.last?.key == route.key) else { return }
routes.append(route)
print("Routing pushed: \(route)")
onPush?(route, animated)
}

public func present(_ route: T, animated: Bool = true, needValidate: Bool = false) {
guard !(needValidate && routes.last?.key == route.key) else { return }
routes.append(route)
print("Routing presented: \(route)")
onPresent?(route, animated)
}

public func pop(animated: Bool = true) {
guard !routes.isEmpty else { return }
let popped = routes.removeLast()
print("Routing popped: \(popped)")
onPopLast?(1, animated)
}

public func popTo(last index: Int, animated: Bool = true) {
guard !routes.isEmpty else { return }
let elementsToRemove = min(index - 1, routes.count - 1)
routes.removeLast(elementsToRemove)
onPopLast?(index, animated)
}

public func popTo(_ route: T, inclusive: Bool = false, animated: Bool = true) {
guard let foundIndex = routes.lastIndex(where: { $0 == route }) else { return }
let indexToPopTo = inclusive ? foundIndex : foundIndex + 1
guard indexToPopTo != 0 else {
popToRoot(index: nil, animated: animated)
return
}

let numToPop = routes.count - indexToPopTo
routes.removeLast(numToPop)
onPopLast?(numToPop, animated)
}

public func popToRoot(index: Int? = nil, animated: Bool = true) {
onPopToRoot?(index, animated)
if routes.count > 1 {
routes.removeSubrange(1 ..< routes.count)
}
}

public func onSystemPop() {
guard !routes.isEmpty else { return }
let popped = routes.removeLast()
Expand All @@ -90,12 +92,12 @@ public final class Routing<T: RouterIdentifiable> {
public struct RouteProvider<T: RouterIdentifiable, Screen: View>: View {
private let router: Routing<T>
@ViewBuilder private let routeMap: (T) -> Screen

public init(_ router: Routing<T>, @ViewBuilder _ routeMap: @escaping (T) -> Screen) {
self.router = router
self.routeMap = routeMap
}

public var body: some View {
NavigationControllerHost(router: router, routeMap: routeMap)
.edgesIgnoringSafeArea(.all)
Expand All @@ -105,35 +107,35 @@ public struct RouteProvider<T: RouterIdentifiable, Screen: View>: View {
struct NavigationControllerHost<T: RouterIdentifiable, Screen: View>: UIViewControllerRepresentable {
let router: Routing<T>
@ViewBuilder var routeMap: (T) -> Screen

func makeUIViewController(context: Context) -> PopAwareUINavigationController {
let navigation = PopAwareUINavigationController()
navigation.navigationBar.isHidden = true
setupRouterCallbacks(in: navigation)
setupInitialRoutes(in: navigation)
return navigation
}

private func setupRouterCallbacks(in navigation: PopAwareUINavigationController) {
navigation.popHandler = { router.onSystemPop() }
navigation.stackSizeProvider = { router.currentRoutes.count }

router.onMakeRoot = { route, animated in
let viewController = UIHostingController(rootView: routeMap(route))
navigation.setViewControllers([viewController], animated: animated)
}

router.onPush = { route, animated in
let viewController = UIHostingController(rootView: routeMap(route))
navigation.pushViewController(viewController, animated: animated)
}

router.onPresent = { route, animated in
let viewController = UIHostingController(rootView: routeMap(route))
viewController.modalPresentationStyle = .overFullScreen
navigation.present(viewController, animated: animated)
rootViewController?.present(viewController, animated: animated)
}

router.onPopLast = { numToPop, animated in
let popTo = navigation.viewControllers.count - numToPop - 1
if numToPop == navigation.viewControllers.count {
Expand All @@ -142,7 +144,7 @@ struct NavigationControllerHost<T: RouterIdentifiable, Screen: View>: UIViewCont
navigation.popToViewController(navigation.viewControllers[popTo], animated: animated)
}
}

router.onPopToRoot = { tabIndex, animated in
navigation.popToRootViewController(animated: animated)
if let tabIndex = tabIndex {
Expand All @@ -154,34 +156,53 @@ struct NavigationControllerHost<T: RouterIdentifiable, Screen: View>: UIViewCont
}
}
}


private var rootViewController: UIViewController? {
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.compactMap { $0.windows.first { $0.isKeyWindow } }
.first?.rootViewController.flatMap(getTopViewController)
}

private func getTopViewController(from viewController: UIViewController) -> UIViewController {
if let presented = viewController.presentedViewController {
return getTopViewController(from: presented)
} else if let navigation = viewController as? UINavigationController {
return getTopViewController(from: navigation.visibleViewController ?? viewController)
} else if let tabBar = viewController as? UITabBarController {
return getTopViewController(from: tabBar.selectedViewController ?? viewController)
} else {
return viewController
}
}

private func setupInitialRoutes(in navigation: PopAwareUINavigationController) {
for path in router.currentRoutes {
navigation.pushViewController(UIHostingController(rootView: routeMap(path)), animated: true)
}
}

func updateUIViewController(_ navigation: PopAwareUINavigationController, context: Context) {
navigation.navigationBar.isHidden = true
}

static func dismantleUIViewController(_ navigation: PopAwareUINavigationController, coordinator: ()) {
navigation.viewControllers = []
navigation.popHandler = nil
}

typealias UIViewControllerType = PopAwareUINavigationController
}

class PopAwareUINavigationController: UINavigationController, UINavigationControllerDelegate {
var popHandler: (() -> Void)?
var stackSizeProvider: (() -> Int)?

override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}

func navigationController(_ navigationController: UINavigationController, didShow _: UIViewController, animated _: Bool) {
if let stackSizeProvider = stackSizeProvider, stackSizeProvider() > navigationController.viewControllers.count {
popHandler?()
Expand Down
78 changes: 47 additions & 31 deletions Giffy/Features/Favorite/FavoriteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ import CommonUI
import ComposableArchitecture

struct FavoriteView: View {
@Environment(\.dismiss) var pop
let store: StoreOf<FavoriteReducer>

var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
NavigationView {
ZStack {
ScrollView(.vertical, showsIndicators: false) {
SearchField { query in
viewStore.send(.fetch(request: query))
}
.padding(.horizontal, 16)
.padding(.vertical, 20)

.padding(.top, 52)

if viewStore.state.list.isEmpty {
FavoriteEmptyView()
.padding(.top, 50)
Expand Down Expand Up @@ -54,42 +56,56 @@ struct FavoriteView: View {
.animation(.easeInOut(duration: 0.2), value: viewStore.list.count)
.navigationBarBackButtonHidden(false)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
IconButton(
iconName: "chevron.left",
tint: .blue,
onClick: {
viewStore.send(.didBackPressed)
}
)
}

ToolbarItem(placement: .principal) {
Text(key: .titleFavorite)
.font(.bold, size: 16)
.showDialog(
shouldDismissOnTapOutside: true,
isShowing: viewStore.binding(
get: { $0.shareImage != nil },
send: .showShare(nil)
)
) {
ShareView(store: viewStore.share)
}
.onAppear {
viewStore.send(.fetch())
}
.onReceive(viewStore.state.detailDisappear) { _ in
viewStore.send(.fetch())
}

VStack {
FavoriteToolbar(title: Localizable.titleFavorite.tr()) {
pop()
}
Spacer()
}
}
.showDialog(
shouldDismissOnTapOutside: true,
isShowing: viewStore.binding(
get: { $0.shareImage != nil },
send: .showShare(nil)
)
) {
ShareView(store: viewStore.share)
}
.onAppear {
viewStore.send(.fetch())
}
.onReceive(viewStore.state.detailDisappear) { _ in
viewStore.send(.fetch())
}
}
}
}

struct FavoriteToolbar: View {
var title: String
var onBackPressed: () -> Void

var body: some View {
HStack {
IconButton(
iconName: "chevron.left",
tint: .Theme.red,
size: 26,
onClick: onBackPressed
)
Spacer()
Text(title)
.font(.system(size: 16, weight: .bold))
Spacer()
Spacer().frame(width: 32)
}
.padding([.horizontal, .bottom], 8)
.background(Blur(style: .prominent).edgesIgnoringSafeArea(.top))
}
}

#Preview {
FavoriteView(store: Injection.resolve())
}
29 changes: 13 additions & 16 deletions Giffy/Features/Home/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ struct RedirectButton: View {
.foregroundColor(.Theme.yellow)
.frame(width: 17, height: 17)
.padding(.all, 17)
.background(Color.Theme.background)
.clipShape(.circle)
.contentShape(.circle)
}.buttonStyle(.plain)
.background(Circle().fill(Color.Theme.background))
}
}
}

Expand All @@ -51,18 +49,17 @@ struct FavoriteButton: View {
var onClick: () -> Void

var body: some View {
Image(systemName: isFavorite ? "heart.fill" : "heart")
.resizable()
.foregroundColor(!isInverted ? Color.Theme.red : Color.white)
.frame(width: size.width - margin, height: size.height - margin - 4)
.background(
(!isInverted ? Color.Theme.background : Color.Theme.red)
.clipShape(Circle())
.frame(width: size.width, height: size.height)
)
.onTapGesture {
onClick()
}
Button(action: onClick) {
Image(systemName: isFavorite ? "heart.fill" : "heart")
.resizable()
.foregroundColor(!isInverted ? Color.Theme.red : Color.white)
.frame(width: size.width - margin, height: size.height - margin - 4)
.background(
(!isInverted ? Color.Theme.background : Color.Theme.red)
.clipShape(.circle)
.frame(width: size.width, height: size.height)
)
}
}

func frame(width: CGFloat, height: CGFloat) -> Self {
Expand Down
3 changes: 1 addition & 2 deletions Giffy/Features/Home/GiffyRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ struct GiffyRow: View {
FavoriteButton(isFavorite: $isFavorite, size: .init(width: 40, height: 40)) {
onFavorite(giphy)
}
.tapScaleEffect()
.padding(.trailing, 10)
}

Expand All @@ -64,6 +63,7 @@ struct GiffyRow: View {
.animation(.linear(duration: 0.2), value: isSelected)
}

@ViewBuilder
var footer: some View {
HStack {
if !giphy.title.isEmpty {
Expand All @@ -84,7 +84,6 @@ struct GiffyRow: View {
RedirectButton(onClick: {
onTapRow?(giphy)
})
.tapScaleEffect()
.showGiffyMenu(
URL(string: giphy.url),
data: downloadedImage,
Expand Down
2 changes: 1 addition & 1 deletion Giffy/Features/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct HomeView: View {
) {
ShareView(store: viewStore.share)
}
.onChange(of: viewStore.shareImage) { image, _ in
.onChange(of: viewStore.shareImage) { _, image in
tabState.isShowShare = image != nil
}
.onAppear {
Expand Down
Loading

0 comments on commit 82feba9

Please sign in to comment.