diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 1655165..e35d179 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; }; D0625E5F250F0CFF00502611 /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5E250F0CFF00502611 /* StatusView.swift */; }; D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; }; + D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; }; D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; }; D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; }; D0B7434925100DBB00C13DB6 /* StatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D0B7434825100DBB00C13DB6 /* StatusView.xib */; }; @@ -103,6 +104,7 @@ D0625E5E250F0CFF00502611 /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; }; D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; D085C3BB25008DEC008A6C5E /* DB */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DB; sourceTree = ""; }; D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = ""; }; D0B32F4F250B373600311912 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = ""; }; @@ -300,6 +302,7 @@ D0C7D43024F76169001EBDBB /* View Controllers */ = { isa = PBXGroup; children = ( + D06BC5E525202AD90079541D /* ProfileViewController.swift */, D0F0B12D251A97E400942152 /* TableViewController.swift */, ); path = "View Controllers"; @@ -539,6 +542,7 @@ D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */, D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */, D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */, + D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */, D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */, D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */, D0F0B10E251A868200942152 /* AccountView.swift in Sources */, diff --git a/View Controllers/ProfileViewController.swift b/View Controllers/ProfileViewController.swift new file mode 100644 index 0000000..6702596 --- /dev/null +++ b/View Controllers/ProfileViewController.swift @@ -0,0 +1,35 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import UIKit +import ViewModels + +final class ProfileViewController: TableViewController { + private let viewModel: ProfileViewModel + private var cancellables = Set() + + required init(viewModel: ProfileViewModel) { + self.viewModel = viewModel + + super.init(viewModel: viewModel) + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Initial size is to avoid unsatisfiable constraint warning + let accountHeaderView = AccountHeaderView(frame: .init(origin: .zero, size: .init(width: 100, height: 100))) + + accountHeaderView.viewModel = viewModel + + viewModel.$accountViewModel + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + accountHeaderView.viewModel = self?.viewModel + self?.sizeTableHeaderFooterViews() + } + .store(in: &cancellables) + + tableView.tableHeaderView = accountHeaderView + } +} diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index a7c2555..5b4e8bc 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -124,13 +124,44 @@ extension TableViewController: UITableViewDataSourcePrefetching { } } +extension TableViewController { + func sizeTableHeaderFooterViews() { + // https://useyourloaf.com/blog/variable-height-table-view-header/ + if let headerView = tableView.tableHeaderView { + let size = headerView.systemLayoutSizeFitting( + CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel) + + if headerView.frame.size.height != size.height { + headerView.frame.size.height = size.height + tableView.tableHeaderView = headerView + tableView.layoutIfNeeded() + } + + view.insertSubview(webfingerIndicatorView, aboveSubview: headerView) + } + + if let footerView = tableView.tableFooterView { + let size = footerView.systemLayoutSizeFitting( + CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel) + + if footerView.frame.size.height != size.height { + footerView.frame.size.height = size.height + tableView.tableFooterView = footerView + tableView.layoutIfNeeded() + } + } + } +} + private extension TableViewController { func setupViewModelBindings() { viewModel.title.sink { [weak self] in self?.navigationItem.title = $0 }.store(in: &cancellables) - viewModel.collectionItems - .sink { [weak self] in self?.update(items: $0) } - .store(in: &cancellables) + viewModel.collectionItems.sink { [weak self] in self?.update(items: $0) }.store(in: &cancellables) viewModel.navigationEvents.receive(on: DispatchQueue.main).sink { [weak self] in guard let self = self else { return } @@ -138,8 +169,10 @@ private extension TableViewController { switch $0 { case let .share(url): self.share(url: url) - case let .collectionNavigation(collectionViewModel): - self.show(TableViewController(viewModel: collectionViewModel), sender: self) + case let .collectionNavigation(viewModel): + self.show(TableViewController(viewModel: viewModel), sender: self) + case let .profileNavigation(viewModel): + self.show(ProfileViewController(viewModel: viewModel), sender: self) case let .urlNavigation(url): self.present(SFSafariViewController(url: url), animated: true) case .webfingerStart: @@ -150,33 +183,13 @@ private extension TableViewController { } .store(in: &cancellables) - viewModel.loading - .receive(on: RunLoop.main) - .sink { [weak self] in - guard let self = self else { return } + viewModel.loading.receive(on: RunLoop.main).sink { [weak self] in + guard let self = self else { return } - self.tableView.tableFooterView = $0 ? self.loadingTableFooterView : UIView() - self.sizeTableHeaderFooterViews() - } - .store(in: &cancellables) - - if let accountsStatusesViewModel = viewModel as? ProfileViewModel { - // Initial size is to avoid unsatisfiable constraint warning - let accountHeaderView = AccountHeaderView( - frame: .init( - origin: .zero, - size: .init(width: 100, height: 100))) - accountHeaderView.viewModel = accountsStatusesViewModel - accountsStatusesViewModel.$accountViewModel - .dropFirst() - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - accountHeaderView.viewModel = accountsStatusesViewModel - self?.sizeTableHeaderFooterViews() - } - .store(in: &cancellables) - tableView.tableHeaderView = accountHeaderView + self.tableView.tableFooterView = $0 ? self.loadingTableFooterView : UIView() + self.sizeTableHeaderFooterViews() } + .store(in: &cancellables) } func update(items: [[CollectionItem]]) { @@ -212,35 +225,4 @@ private extension TableViewController { present(activityViewController, animated: true, completion: nil) } - - func sizeTableHeaderFooterViews() { - // https://useyourloaf.com/blog/variable-height-table-view-header/ - if let headerView = tableView.tableHeaderView { - let size = headerView.systemLayoutSizeFitting( - CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), - withHorizontalFittingPriority: .required, - verticalFittingPriority: .fittingSizeLevel) - - if headerView.frame.size.height != size.height { - headerView.frame.size.height = size.height - tableView.tableHeaderView = headerView - tableView.layoutIfNeeded() - } - - view.insertSubview(webfingerIndicatorView, aboveSubview: headerView) - } - - if let footerView = tableView.tableFooterView { - let size = footerView.systemLayoutSizeFitting( - CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), - withHorizontalFittingPriority: .required, - verticalFittingPriority: .fittingSizeLevel) - - if footerView.frame.size.height != size.height { - footerView.frame.size.height = size.height - tableView.tableFooterView = footerView - tableView.layoutIfNeeded() - } - } - } } diff --git a/ViewModels/Sources/ViewModels/AccountListViewModel.swift b/ViewModels/Sources/ViewModels/AccountListViewModel.swift index b95560a..6023af1 100644 --- a/ViewModels/Sources/ViewModels/AccountListViewModel.swift +++ b/ViewModels/Sources/ViewModels/AccountListViewModel.swift @@ -74,7 +74,7 @@ extension AccountListViewModel: CollectionViewModel { profileService = navigationService.profileService(id: item.id) } - navigationEventsSubject.send(.collectionNavigation(ProfileViewModel(profileService: profileService))) + navigationEventsSubject.send(.profileNavigation(ProfileViewModel(profileService: profileService))) default: break } diff --git a/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift b/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift index 833581c..191d844 100644 --- a/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift +++ b/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift @@ -4,6 +4,7 @@ import Foundation public enum NavigationEvent { case collectionNavigation(CollectionViewModel) + case profileNavigation(ProfileViewModel) case urlNavigation(URL) case share(URL) case webfingerStart @@ -22,7 +23,7 @@ extension NavigationEvent { case let .statusList(statusListService): self = .collectionNavigation(StatusListViewModel(statusListService: statusListService)) case let .profile(profileService): - self = .collectionNavigation(ProfileViewModel(profileService: profileService)) + self = .profileNavigation(ProfileViewModel(profileService: profileService)) case .webfingerStart: self = .webfingerStart case .webfingerEnd: