Skip to content

Commit

Permalink
Rewrite SyllabusWebView (#318)
Browse files Browse the repository at this point in the history
* Rewrite SyllabusWebView

* Apply SwiftFormat changes

* Fix as reviews

---------

Co-authored-by: shp7724 <[email protected]>
  • Loading branch information
shp7724 and shp7724 authored Oct 22, 2024
1 parent 702ad8a commit 13a1ee3
Show file tree
Hide file tree
Showing 7 changed files with 421 additions and 47 deletions.
46 changes: 21 additions & 25 deletions SNUTT-2022/SNUTT.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@
CEB5A71E29DD9CD400E74C47 /* TimetableAccessoryRectangularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB5A71D29DD9CD400E74C47 /* TimetableAccessoryRectangularView.swift */; };
CEB5A72029DDA8B200E74C47 /* TimetableAccessoryInlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB5A71F29DDA8B200E74C47 /* TimetableAccessoryInlineView.swift */; };
CEC91CAA2A812499008B0F15 /* VacancyEmptyListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC91CA92A812499008B0F15 /* VacancyEmptyListView.swift */; };
CECDD36C2CC3E45000BB5E5B /* SyllabusFilePreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECDD36B2CC3E44F00BB5E5B /* SyllabusFilePreviewController.swift */; };
CECDD36E2CC3E47300BB5E5B /* WebBottomNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECDD36D2CC3E47000BB5E5B /* WebBottomNavigationView.swift */; };
CECDD3702CC3E4B400BB5E5B /* WebProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECDD36F2CC3E4B000BB5E5B /* WebProgressView.swift */; };
CECE96642A72D5F2003D87FA /* VacancyBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECE96632A72D5F2003D87FA /* VacancyBanner.swift */; };
CEDDCA7B2A6AE62B00474D4E /* VacancyLectureCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDCA7A2A6AE62B00474D4E /* VacancyLectureCell.swift */; };
CEDDCA7E2A6AEF2200474D4E /* VacancyLectureList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDCA7D2A6AEF2200474D4E /* VacancyLectureList.swift */; };
Expand Down Expand Up @@ -591,6 +594,9 @@
CEB5A71D29DD9CD400E74C47 /* TimetableAccessoryRectangularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimetableAccessoryRectangularView.swift; sourceTree = "<group>"; };
CEB5A71F29DDA8B200E74C47 /* TimetableAccessoryInlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimetableAccessoryInlineView.swift; sourceTree = "<group>"; };
CEC91CA92A812499008B0F15 /* VacancyEmptyListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VacancyEmptyListView.swift; sourceTree = "<group>"; };
CECDD36B2CC3E44F00BB5E5B /* SyllabusFilePreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyllabusFilePreviewController.swift; sourceTree = "<group>"; };
CECDD36D2CC3E47000BB5E5B /* WebBottomNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebBottomNavigationView.swift; sourceTree = "<group>"; };
CECDD36F2CC3E4B000BB5E5B /* WebProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebProgressView.swift; sourceTree = "<group>"; };
CECE96632A72D5F2003D87FA /* VacancyBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VacancyBanner.swift; sourceTree = "<group>"; };
CEDDCA7A2A6AE62B00474D4E /* VacancyLectureCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VacancyLectureCell.swift; sourceTree = "<group>"; };
CEDDCA7D2A6AEF2200474D4E /* VacancyLectureList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VacancyLectureList.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -725,7 +731,7 @@
BE28036328E8850E00B2B1AB /* WebViewType.swift */,
BE28036128E884F400B2B1AB /* ReviewWebView.swift */,
BE28036528E8854D00B2B1AB /* SingleWebView.swift */,
B89826D028F4971000477A14 /* SyllabusWebView.swift */,
CECDD36A2CC3E43400BB5E5B /* SyllabusWebView */,
B8F40EAE28980D990021A2A9 /* DeveloperInfoView.swift */,
B8F40EB028980DE00021A2A9 /* TermsOfServiceView.swift */,
B8F40EB228980DFF0021A2A9 /* PrivacyPolicyView.swift */,
Expand Down Expand Up @@ -876,6 +882,17 @@
path = Lecture;
sourceTree = "<group>";
};
CECDD36A2CC3E43400BB5E5B /* SyllabusWebView */ = {
isa = PBXGroup;
children = (
B89826D028F4971000477A14 /* SyllabusWebView.swift */,
CECDD36B2CC3E44F00BB5E5B /* SyllabusFilePreviewController.swift */,
CECDD36D2CC3E47000BB5E5B /* WebBottomNavigationView.swift */,
CECDD36F2CC3E4B000BB5E5B /* WebProgressView.swift */,
);
path = SyllabusWebView;
sourceTree = "<group>";
};
CEDDCA7C2A6AEF0E00474D4E /* Vacancy */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1227,7 +1244,6 @@
DC860F4227E5C87D0068C94B /* Frameworks */,
DC860F4327E5C87D0068C94B /* Resources */,
BE98A073288AFC1600C2CE95 /* Embed Foundation Extensions */,
BEBB3C14292CCD960023F695 /* Upload dSym to Firebase */,
);
buildRules = (
);
Expand Down Expand Up @@ -1388,29 +1404,6 @@
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
BEBB3C14292CCD960023F695 /* Upload dSym to Firebase */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}",
"$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)",
);
name = "Upload dSym to Firebase";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nif [ \"${CONFIGURATION}\" = \"Release\" ]; \nthen \n${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run -gsp \"${PROJECT_DIR}\"/SNUTT/GoogleServiceReleaseInfo.plist\n${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols -gsp \"${PROJECT_DIR}\"/SNUTT/GoogleServiceReleaseInfo.plist -p ios ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\nelse\n${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run -gsp \"${PROJECT_DIR}\"/SNUTT/GoogleServiceDebugInfo.plist\n${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols -gsp \"${PROJECT_DIR}\"/SNUTT/GoogleServiceDebugInfo.plist -p ios ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
BE98A05D288AFC1500C2CE95 /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand Down Expand Up @@ -1505,6 +1498,7 @@
B87DF6F129150A00008BB95B /* PopupService.swift in Sources */,
DC29159D2865FA2800FE5F9A /* SettingViewModel.swift in Sources */,
DC1E0ED12877381F005632A3 /* Router.swift in Sources */,
CECDD36E2CC3E47300BB5E5B /* WebBottomNavigationView.swift in Sources */,
B87B315D28D5A70F005C170B /* UserState.swift in Sources */,
B87DF6F92918B7AD008BB95B /* PopupState.swift in Sources */,
B87B316D28D755B6005C170B /* MenuState.swift in Sources */,
Expand All @@ -1525,6 +1519,7 @@
DC1E0ECF28772F13005632A3 /* NetworkUtils.swift in Sources */,
BE6D893528EFD68E000607A6 /* String+Localized.swift in Sources */,
BEB3B6A228CDB7A400E56062 /* LectureTimePicker.swift in Sources */,
CECDD3702CC3E4B400BB5E5B /* WebProgressView.swift in Sources */,
BE77D0AC28B1384D0067A9D8 /* MenuSheetTopBar.swift in Sources */,
BEDF506F27EB744A00CDCC13 /* LectureDetailScene.swift in Sources */,
BE98A06F288AFC1600C2CE95 /* SNUTTWidget.intentdefinition in Sources */,
Expand Down Expand Up @@ -1627,6 +1622,7 @@
BE9413D928C3AFA500171060 /* EmptySearchResult.swift in Sources */,
CE72435F2B30235300F9E0D7 /* SearchLectureSceneViewModel.swift in Sources */,
CEF4200F2A62ADE3005C2B1F /* FriendsViewModel.swift in Sources */,
CECDD36C2CC3E45000BB5E5B /* SyllabusFilePreviewController.swift in Sources */,
B88D16F328ABC5DD00E2D652 /* String+Ext.swift in Sources */,
BEDE34D62879A7B800525014 /* DIContainer.swift in Sources */,
BE9413D228C2458A00171060 /* DateFormatter+Parse.swift in Sources */,
Expand Down
19 changes: 0 additions & 19 deletions SNUTT-2022/SNUTT/Views/Components/WebViews/SyllabusWebView.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// SyllabusFilePreviewController.swift
// SNUTT
//
// Created by 박신홍 on 10/19/24.
//

import QuickLook

final class SyllabusFilePreviewController: QLPreviewController {
private let item: any QLPreviewItem
init(item: any QLPreviewItem) {
self.item = item
super.init(nibName: nil, bundle: nil)
dataSource = self
}

@available(*, unavailable) required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

extension SyllabusFilePreviewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in _: QLPreviewController) -> Int {
1
}

func previewController(_: QLPreviewController, previewItemAt _: Int) -> any QLPreviewItem {
item
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//
// SyllabusWebView.swift
// SNUTT
//
// Created by 최유림 on 2022/10/11.
//

import Combine
import QuickLook
import SwiftUI
import UIKit
import WebKit

struct SyllabusWebView: UIViewControllerRepresentable {
let lectureTitle: String
@Binding var urlString: String

func makeUIViewController(context _: UIViewControllerRepresentableContext<Self>) -> UIViewController {
let webViewController = SyllabusWebViewController(entryURL: URL(string: urlString))
let navigationController = UINavigationController(rootViewController: webViewController)
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
navigationController.navigationBar.standardAppearance = appearance
navigationController.navigationBar.scrollEdgeAppearance = appearance
webViewController.navigationItem.rightBarButtonItem = .init(systemItem: .close, primaryAction: .init(handler: { [weak navigationController] _ in
navigationController?.dismiss(animated: true)
}))
webViewController.title = lectureTitle
return navigationController
}

func updateUIViewController(_: UIViewController, context _: UIViewControllerRepresentableContext<Self>) {}
}

final class SyllabusWebViewController: UIViewController {
private enum Constants {
static let homeURL = URL(string: "https://sugang.snu.ac.kr")!
static let referer = "https://sugang.snu.ac.kr/sugang/cc/cc100InterfaceExcel.action"
}

private enum Design {
static let bottomViewHeight = 50.0
}

private let entryURL: URL
private lazy var webView: WKWebView = {
let config = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: config)
webView.allowsBackForwardNavigationGestures = true
webView.scrollView.contentInset = .init(top: 0, left: 0, bottom: Design.bottomViewHeight, right: 0)
webView.navigationDelegate = self
return webView
}()

private var cancellables = Set<AnyCancellable>()
private let bottomView = WebBottomNavigationView()
private var downloadLocalURLs = [WKDownload: URL]()

init(entryURL: URL?) {
self.entryURL = entryURL ?? Constants.homeURL
super.init(nibName: nil, bundle: nil)
}

@available(*, unavailable) required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
setupView()
bindBottomButtons()
initialLoad()
}

private func setupView() {
view.addSubview(webView)
view.addSubview(bottomView)
webView.translatesAutoresizingMaskIntoConstraints = false
bottomView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
bottomView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
bottomView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bottomView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
bottomView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -Design.bottomViewHeight),
])
}

private func initialLoad() {
var request = URLRequest(url: entryURL)
request.setValue(Constants.referer, forHTTPHeaderField: "Referer")
webView.load(request)
}

private func bindBottomButtons() {
bottomView.buttonDidPressPublisher
.sink { [weak self] button in
guard let self else { return }
switch button {
case .back:
webView.goBack()
case .forward:
webView.goForward()
case .reload:
webView.reload()
case .safari:
UIApplication.shared.open(entryURL)
}
}
.store(in: &cancellables)
webView.publisher(for: \.canGoBack)
.sink { [weak self] canGoBack in
guard let self else { return }
bottomView.button(for: .back).isEnabled = canGoBack
}
.store(in: &cancellables)
webView.publisher(for: \.canGoForward)
.sink { [weak self] canGoForward in
guard let self else { return }
bottomView.button(for: .forward).isEnabled = canGoForward
}
.store(in: &cancellables)
webView.publisher(for: \.estimatedProgress)
.sink { [weak self] progress in
guard let self else { return }
bottomView.progressView.setProgress(progress)
print("[progress] \(progress)")
}
.store(in: &cancellables)
}
}

extension SyllabusWebViewController: WKNavigationDelegate {
func webView(_: WKWebView, decidePolicyFor _: WKNavigationAction) async -> WKNavigationActionPolicy {
return .allow
}

func webView(_: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse) async -> WKNavigationResponsePolicy {
guard let mimeType = navigationResponse.response.mimeType else { return .cancel }
if mimeType == "text/html" {
return .allow
}
return .download
}

func webView(_: WKWebView, navigationResponse _: WKNavigationResponse, didBecome download: WKDownload) {
download.delegate = self
}
}

extension SyllabusWebViewController: WKDownloadDelegate {
func download(_ download: WKDownload, decideDestinationUsing _: URLResponse, suggestedFilename: String) async -> URL? {
let percentDecoded = suggestedFilename.removingPercentEncoding ?? suggestedFilename
let fileManager = FileManager.default
let url = fileManager.temporaryDirectory.appendingPathComponent(percentDecoded)
try? fileManager.removeItem(at: url)
downloadLocalURLs[download] = url
return url
}

func downloadDidFinish(_ download: WKDownload) {
guard let localURL = downloadLocalURLs.removeValue(forKey: download) else { return }
let previewController = SyllabusFilePreviewController(item: localURL as QLPreviewItem)
present(previewController, animated: true)
}
}

extension UIControl {
func addAction(
for controlEvents: UIControl.Event = .touchUpInside,
_ closure: @MainActor @escaping () -> Void
) {
addAction(UIAction { (_: UIAction) in closure() }, for: controlEvents)
}
}

@available(iOS 17.0, *)
#Preview {
SyllabusWebView(lectureTitle: "수강스누", urlString: .constant("https://sugang.snu.ac.kr/sugang/cc/cc103.action?openSchyy=2024&openShtmFg=U000200002&openDetaShtmFg=U000300002&sbjtCd=M3500.002000&ltNo=001&sbjtSubhCd=000"))
.ignoresSafeArea(edges: .bottom)
}

@available(iOS 17.0, *)
#Preview {
SyllabusWebView(lectureTitle: "네이버", urlString: .constant("https://naver.com"))
.ignoresSafeArea(edges: .bottom)
}
Loading

0 comments on commit 13a1ee3

Please sign in to comment.