From cc369fb9ad088e9fb204bc2ad547c245aa7ba7ad Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 14 Apr 2024 03:25:39 +0900 Subject: [PATCH 01/14] style: share extension assets --- .../Media.xcassets/128.imageset/128 1.png | Bin 0 -> 7717 bytes .../Media.xcassets/128.imageset/128.png | Bin 0 -> 7717 bytes .../Media.xcassets/128.imageset/Contents.json | 22 ++++++++++++++++++ .../Resources/Media.xcassets/Contents.json | 6 +++++ 4 files changed, 28 insertions(+) create mode 100644 ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png create mode 100644 ShareExtension/Resources/Media.xcassets/128.imageset/128.png create mode 100644 ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json create mode 100644 ShareExtension/Resources/Media.xcassets/Contents.json diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png b/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png new file mode 100644 index 0000000000000000000000000000000000000000..077d76c18344fce066fb9f879587ff94f3e58f46 GIT binary patch literal 7717 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 Date: Sun, 14 Apr 2024 03:26:46 +0900 Subject: [PATCH 02/14] feat: change share extension view --- Project.swift | 8 +- ShareExtension/Model/Metadata.swift | 12 -- ShareExtension/ShareViewController.swift | 182 ------------------ .../ShareExtension+UIColor+Extension.swift | 22 +++ .../Extension/UIbutton+Extension.swift | 22 +++ .../Sources/ShareViewController.swift | 152 +++++++++++++++ .../View/ShareExtensionBackGroundView.swift | 145 ++++++++++++++ .../View/ShareExtensionBackGroundView.swift | 108 ----------- 8 files changed, 345 insertions(+), 306 deletions(-) delete mode 100644 ShareExtension/Model/Metadata.swift delete mode 100644 ShareExtension/ShareViewController.swift create mode 100644 ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift create mode 100644 ShareExtension/Sources/Extension/UIbutton+Extension.swift create mode 100644 ShareExtension/Sources/ShareViewController.swift create mode 100644 ShareExtension/Sources/View/ShareExtensionBackGroundView.swift delete mode 100644 ShareExtension/View/ShareExtensionBackGroundView.swift diff --git a/Project.swift b/Project.swift index b358e2a..dabac2a 100644 --- a/Project.swift +++ b/Project.swift @@ -18,12 +18,12 @@ class iBoxFactory: ProjectFactory { let dependencies: [TargetDependency] = [ .external(name: "SnapKit"), + .external(name: "SwiftSoup"), .target(name: "iBoxShareExtension") ] let iBoxShareExtensionDependencies: [TargetDependency] = [ - .external(name: "SnapKit"), - .external(name: "SwiftSoup") + .external(name: "SnapKit") ] private let appInfoPlist: [String: Plist.Value] = [ @@ -92,8 +92,8 @@ class iBoxFactory: ProjectFactory { bundleId: "\(bundleId).ShareExtension", deploymentTarget: .iOS(targetVersion: iosVersion, devices: [.iphone]), infoPlist: .extendingDefault(with: shareExtensionInfoPlist), - sources: ["ShareExtension/**"], - resources: [], + sources: ["ShareExtension/Sources/**"], + resources: ["ShareExtension/Resources/**"], dependencies: iBoxShareExtensionDependencies ) diff --git a/ShareExtension/Model/Metadata.swift b/ShareExtension/Model/Metadata.swift deleted file mode 100644 index 8f95deb..0000000 --- a/ShareExtension/Model/Metadata.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Metadata.swift -// iBoxShareExtension -// -// Created by 김찬희 on 2024/03/14. -// - -struct Metadata { - var title: String? - var faviconUrl: String? - var url: String? -} diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift deleted file mode 100644 index d7d0a2b..0000000 --- a/ShareExtension/ShareViewController.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// ShareViewController.swift -// iBoxWebShareExtension -// -// Created by Chan on 2/8/24. -// - -import UIKit -import Social -import UniformTypeIdentifiers - -import SnapKit -import SwiftSoup - -@objc(CustomShareViewController) -class CustomShareViewController: UIViewController { - - var backgroundView = ShareExtensionBackGroundView() - var dataURL: String? - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupProperty() - setupHierarchy() - setupLayout() - extractSharedURL() - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundView.delegate = self - } - - private func setupHierarchy() { - view.addSubview(backgroundView) - } - - private func setupLayout() { - backgroundView.snp.makeConstraints { make in - make.trailing.leading.equalToSuperview().inset(30) - make.center.equalToSuperview().offset(-20) - make.height.equalTo(120) - } - } - - func getAppGroupUserDefaults() { - let defaults = UserDefaults(suiteName: "group.com.iBox") - if let data = defaults?.string(forKey: "share") { - print("ShareViewController: URL 가져오기: \(data)") - } - } - - func hideExtensionWithCompletionHandler(completion: @escaping (Bool) -> Void) { - UIView.animate(withDuration: 0.3, animations: { - self.navigationController?.view.transform = CGAffineTransform(translationX: 0, y:self.navigationController!.view.frame.size.height) - }, completion: completion) - } - - // MARK: IBAction - - @IBAction func cancel() { - self.hideExtensionWithCompletionHandler(completion: { _ in - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) - }) - } - - @objc func openURL(_ url: URL) -> Bool { - self.hideExtensionWithCompletionHandler(completion: { _ in - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) - }) - - var responder: UIResponder? = self - while responder != nil { - if let application = responder as? UIApplication { - return application.perform(#selector(openURL(_:)), with: url) != nil - } - responder = responder?.next - } - return false - } - - func extractSharedURL() { - guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { return } - - for attachment in extensionItem.attachments ?? [] { - if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { - attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (data, error) in - DispatchQueue.main.async { - if let url = data as? URL, error == nil { - self?.dataURL = url.absoluteString - print("Shared URL: \(url.absoluteString)") - } else { - print("Failed to retrieve URL: \(String(describing: error))") - } - } - } - break - } - } - } - - func fetchAndParseMetadata(from url: URL, completion: @escaping (Metadata) -> Void) { - URLSession.shared.dataTask(with: url) { data, response, error in - guard let data = data, error == nil else { - print("Failed to fetch data: \(String(describing: error))") - return - } - - let encodingName = (response as? HTTPURLResponse)?.textEncodingName ?? "utf-8" - let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) - - if let htmlContent = String(data: data, encoding: encoding) { - do { - let doc: Document = try SwiftSoup.parse(htmlContent) - let title: String? = try doc.title() - - let faviconSelectors = ["link[rel='shortcut icon']", "link[rel='icon']", "link[rel='apple-touch-icon']"] - var faviconUrl: String? = nil - - for selector in faviconSelectors { - if let faviconLink: Element = try doc.select(selector).first() { - if var href = try? faviconLink.attr("href"), !href.isEmpty { - if href.starts(with: "/") { - href = url.scheme! + "://" + url.host! + href - } else if !href.starts(with: "http") { - href = url.scheme! + "://" + url.host! + "/" + href - } - faviconUrl = href - break - } - } - } - - if faviconUrl == nil { - faviconUrl = url.scheme! + "://" + url.host! + "/favicon.ico" - } - - let decodedUrlString = url.absoluteString.removingPercentEncoding ?? url.absoluteString - let metadata = Metadata(title: title, faviconUrl: faviconUrl, url: decodedUrlString) - - DispatchQueue.main.async { - completion(metadata) - } - } catch { - print("Failed to parse HTML: \(error.localizedDescription)") - } - } - }.resume() - } -} - -extension CustomShareViewController: ShareExtensionBackGroundViewDelegate { - - func didTapCancel() { - cancel() - } - - func didTapOpenApp() { - guard let sharedURL = dataURL, let url = URL(string: sharedURL) else { - print("Share extension error") - return - } - fetchAndParseMetadata(from: url) { metadata in - dump(metadata) - let encodedTitle = metadata.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" - let encodedData = metadata.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" - let encodedFaviconUrl = metadata.faviconUrl?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" - let urlString = "iBox://url?title=\(encodedTitle)&data=\(encodedData)&faviconUrl=\(encodedFaviconUrl)" - - if let openUrl = URL(string: urlString) { - if self.openURL(openUrl) { - print("iBox 앱이 성공적으로 열렸습니다.") - } else { - print("iBox 앱을 열 수 없습니다.") - } - } - } - } -} diff --git a/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift b/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift new file mode 100644 index 0000000..817e21e --- /dev/null +++ b/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift @@ -0,0 +1,22 @@ +// +// ShareExtension+UIColor+Extension.swift +// iBoxShareExtension +// +// Created by Chan on 4/14/24. +// + +import UIKit + +extension UIColor { + + convenience init(hex: UInt, alpha: CGFloat = 1.0) { + self.init( + red: CGFloat((hex & 0xFF0000) >> 16) / 255.0, + green: CGFloat((hex & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(hex & 0x0000FF) / 255.0, + alpha: CGFloat(alpha) + ) + } + + static let box2 = UIColor(hex: 0xFF9548) +} diff --git a/ShareExtension/Sources/Extension/UIbutton+Extension.swift b/ShareExtension/Sources/Extension/UIbutton+Extension.swift new file mode 100644 index 0000000..385e206 --- /dev/null +++ b/ShareExtension/Sources/Extension/UIbutton+Extension.swift @@ -0,0 +1,22 @@ +// +// UIbutton+Extension.swift +// iBoxShareExtension +// +// Created by Chan on 4/14/24. +// + +import UIKit + +extension UIButton { + func setBackgroundColor(_ color: UIColor, for state: UIControl.State) { + UIGraphicsBeginImageContext(CGSize(width: 1.0, height: 1.0)) + guard let context = UIGraphicsGetCurrentContext() else { return } + context.setFillColor(color.cgColor) + context.fill(CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) + + let backgroundImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + setBackgroundImage(backgroundImage, for: state) + } +} diff --git a/ShareExtension/Sources/ShareViewController.swift b/ShareExtension/Sources/ShareViewController.swift new file mode 100644 index 0000000..5e7b22b --- /dev/null +++ b/ShareExtension/Sources/ShareViewController.swift @@ -0,0 +1,152 @@ +// +// ShareViewController.swift +// iBoxWebShareExtension +// +// Created by Chan on 2/8/24. +// + +import UIKit +import Social +import UniformTypeIdentifiers + +import SnapKit +import SwiftSoup + +@objc(CustomShareViewController) +class CustomShareViewController: UIViewController { + + var dataURL: String? + var backgroundView = ShareExtensionBackGroundView() + var modalView: UIView = { + let modalview = UIView() + modalview.backgroundColor = UIColor.lightGray.withAlphaComponent(0.1) + return modalview + }() + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupProperty() + setupHierarchy() + setupLayout() + extractSharedURL() + setupModal() + } + + // MARK: - Setup Methods + + private func setupProperty() { + backgroundView.delegate = self + } + + private func setupHierarchy() { + view.addSubview(modalView) + modalView.addSubview(backgroundView) + } + + private func setupLayout() { + modalView.snp.makeConstraints { make in + make.top.bottom.leading.trailing.equalToSuperview() + } + + backgroundView.snp.makeConstraints { make in + make.trailing.leading.equalToSuperview().inset(30) + make.center.equalToSuperview().offset(-20) + make.height.equalTo(140) + } + } + + private func setupModal() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap(_:))) + tapGesture.cancelsTouchesInView = false + tapGesture.delegate = self + modalView.addGestureRecognizer(tapGesture) + } + + func hideExtensionWithCompletionHandler(completion: @escaping (Bool) -> Void) { + UIView.animate(withDuration: 0.3, animations: { + self.navigationController?.view.transform = CGAffineTransform(translationX: 0, y:self.navigationController!.view.frame.size.height) + }, completion: completion) + } + + func extractSharedURL() { + guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { return } + + for attachment in extensionItem.attachments ?? [] { + if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (data, error) in + DispatchQueue.main.async { + if let url = data as? URL, error == nil { + self?.dataURL = url.absoluteString + print("Shared URL: \(url.absoluteString)") + } else { + print("Failed to retrieve URL: \(String(describing: error))") + } + } + } + break + } + } + } + + // MARK: IBAction + + @IBAction func cancel() { + self.hideExtensionWithCompletionHandler(completion: { _ in + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + }) + } + + @objc func openURL(_ url: URL) -> Bool { + self.hideExtensionWithCompletionHandler(completion: { _ in + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + }) + + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + return application.perform(#selector(openURL(_:)), with: url) != nil + } + responder = responder?.next + } + return false + } + + @objc func handleBackgroundTap(_ sender: UITapGestureRecognizer) { + let location = sender.location(in: self.view) + if !backgroundView.frame.contains(location) { + cancel() + } + } +} + +extension CustomShareViewController: ShareExtensionBackGroundViewDelegate { + + func didTapCancel() { + cancel() + } + + func didTapOpenApp() { + guard let sharedURL = dataURL, let url = URL(string: sharedURL) else { + print("Share extension error") + return + } + + let urlString = "iBox://url?data=\(sharedURL)" + + if let openUrl = URL(string: urlString) { + if self.openURL(openUrl) { + print("iBox 앱이 성공적으로 열렸습니다.") + } else { + print("iBox 앱을 열 수 없습니다.") + } + } + } +} + +extension CustomShareViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } +} diff --git a/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift b/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift new file mode 100644 index 0000000..d8bf165 --- /dev/null +++ b/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift @@ -0,0 +1,145 @@ +// +// BackGroundView.swift +// iBox +// +// Created by Chan on 2/19/24. +// + +import UIKit + +import SnapKit + +protocol ShareExtensionBackGroundViewDelegate: AnyObject { + func didTapCancel() + func didTapOpenApp() +} + +class ShareExtensionBackGroundView: UIView { + + // MARK: - Properties + weak var delegate: ShareExtensionBackGroundViewDelegate? + + // MARK: - UI Components + lazy var stackView: UIStackView = { + let stack = UIStackView() + stack.axis = .horizontal + stack.distribution = .fillProportionally + stack.spacing = 10 + return stack + }() + + lazy var logoImageView: UIImageView = { + let logoImageView = UIImageView() + logoImageView.image = UIImage(named: "128") + logoImageView.contentMode = .scaleAspectFit + logoImageView.setContentHuggingPriority(.required, for: .horizontal) + logoImageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return logoImageView + }() + + lazy var label: UILabel = { + let label = UILabel() + label.text = "이 링크를 iBox 앱에서 여시겠습니까?" + label.font = .systemFont(ofSize: 15) + label.textColor = .label + label.numberOfLines = 3 + label.setContentHuggingPriority(.defaultLow, for: .horizontal) + return label + }() + + lazy var cancelButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = .systemGray3 + button.setContentHuggingPriority(.required, for: .horizontal) + button.setContentCompressionResistancePriority(.required, for: .horizontal) + return button + }() + + lazy var divider: UIView = { + let view = UIView() + view.backgroundColor = .lightGray + view.layer.opacity = 0.2 + return view + }() + + lazy var openAppButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "arrow.up.forward.square"), for: .normal) + button.setTitle("앱으로 담아가기", for: .normal) + button.setTitleColor(.black, for: .normal) + button.setBackgroundColor(.clear, for: .normal) + + button.setTitle("앱이 실행됩니다", for: .highlighted) + button.setTitleColor(.white, for: .highlighted) + button.setBackgroundColor(.box2, for: .highlighted) + button.setImage(UIImage(systemName: "heart.fill"), for: .highlighted) + + button.imageView?.contentMode = .scaleAspectFit + button.tintColor = .box2 + + let spacing: CGFloat = 5 + button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing) + button.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0) + + return button + }() + + // MARK: - Initializer + override init(frame: CGRect) { + super.init(frame: frame) + setupProperty() + setupHierarchy() + setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup Methods + private func setupProperty() { + backgroundColor = .systemBackground + clipsToBounds = true + layer.cornerRadius = 15 + + cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + openAppButton.addTarget(self, action: #selector(openAppButtonTapped), for: .touchUpInside) + } + + private func setupHierarchy() { + addSubview(stackView) + stackView.addArrangedSubview(logoImageView) + stackView.addArrangedSubview(label) + stackView.addArrangedSubview(cancelButton) + + addSubview(divider) + addSubview(openAppButton) + } + + private func setupLayout() { + stackView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview().inset(20) + } + + divider.snp.makeConstraints { make in + make.top.equalTo(stackView.snp.bottom).offset(10) + make.leading.trailing.equalToSuperview() + make.height.equalTo(1) + } + + openAppButton.snp.makeConstraints { make in + make.top.equalTo(divider.snp.bottom) + make.width.leading.trailing.bottom.equalToSuperview() + } + } + + // MARK: - Action Functions + @objc func cancelButtonTapped() { + delegate?.didTapCancel() + } + + @objc func openAppButtonTapped() { + delegate?.didTapOpenApp() + } +} diff --git a/ShareExtension/View/ShareExtensionBackGroundView.swift b/ShareExtension/View/ShareExtensionBackGroundView.swift deleted file mode 100644 index 04a4b3a..0000000 --- a/ShareExtension/View/ShareExtensionBackGroundView.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// BackGroundView.swift -// iBox -// -// Created by Chan on 2/19/24. -// - -import UIKit - -import SnapKit - -protocol ShareExtensionBackGroundViewDelegate: AnyObject { - func didTapCancel() - func didTapOpenApp() -} - -class ShareExtensionBackGroundView: UIView { - - // MARK: - Properties - - weak var delegate: ShareExtensionBackGroundViewDelegate? - - // MARK: - UI Components - - lazy var label: UILabel = { - let label = UILabel() - label.text = "이 링크를 iBox 앱에서 여시겠습니까?" - label.font = .systemFont(ofSize: 15) - label.textColor = .label - return label - }() - - lazy var cancelButton: UIButton = { - let button = UIButton() - button.configuration = .plain() - button.configuration?.attributedTitle = .init( - "Cancel", - attributes: .init([.font: UIFont.systemFont(ofSize: 13)]) - ) - return button - }() - - lazy var openAppButton: UIButton = { - let button = UIButton() - button.configuration = .plain() - button.configuration?.attributedTitle = .init( - "Open", - attributes: .init([.font: UIFont.boldSystemFont(ofSize: 13)]) - ) - return button - }() - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .systemBackground - clipsToBounds = true - layer.cornerRadius = 15 - - cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) - openAppButton.addTarget(self, action: #selector(openAppButtonTapped), for: .touchUpInside) - } - - private func setupHierarchy() { - addSubview(label) - addSubview(cancelButton) - addSubview(openAppButton) - } - - private func setupLayout() { - label.snp.makeConstraints { make in - make.top.equalToSuperview().inset(25) - make.leading.equalToSuperview().inset(20) - } - - cancelButton.snp.makeConstraints { make in - make.trailing.equalTo(openAppButton.snp.leading) - make.centerY.equalTo(openAppButton.snp.centerY) - } - - openAppButton.snp.makeConstraints { make in - make.trailing.bottom.equalToSuperview().inset(15) - } - } - - // MARK: - Action Functions - - @objc func cancelButtonTapped() { - delegate?.didTapCancel() - } - - @objc func openAppButtonTapped() { - delegate?.didTapOpenApp() - } -} From fcc410c3e853bc207e265fd173f6f7ae20cb6ebf Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 14 Apr 2024 03:28:50 +0900 Subject: [PATCH 03/14] fix: separte addbookmark function --- .../AddBookmark/AddBookmarkView.swift | 0 .../AddBookmarkViewController.swift | 71 ++++++++++++++++++- .../AddBookmark/FolderListCell.swift | 0 .../AddBookmark/FolderListView.swift | 0 .../FolderListViewController.swift | 0 iBox/Sources/Model/Metadata.swift | 12 ++++ 6 files changed, 82 insertions(+), 1 deletion(-) rename iBox/Sources/{BoxList => }/AddBookmark/AddBookmarkView.swift (100%) rename iBox/Sources/{BoxList => }/AddBookmark/AddBookmarkViewController.swift (63%) rename iBox/Sources/{BoxList => }/AddBookmark/FolderListCell.swift (100%) rename iBox/Sources/{BoxList => }/AddBookmark/FolderListView.swift (100%) rename iBox/Sources/{BoxList => }/AddBookmark/FolderListViewController.swift (100%) create mode 100644 iBox/Sources/Model/Metadata.swift diff --git a/iBox/Sources/BoxList/AddBookmark/AddBookmarkView.swift b/iBox/Sources/AddBookmark/AddBookmarkView.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/AddBookmarkView.swift rename to iBox/Sources/AddBookmark/AddBookmarkView.swift diff --git a/iBox/Sources/BoxList/AddBookmark/AddBookmarkViewController.swift b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift similarity index 63% rename from iBox/Sources/BoxList/AddBookmark/AddBookmarkViewController.swift rename to iBox/Sources/AddBookmark/AddBookmarkViewController.swift index a7154d0..c7e51c6 100644 --- a/iBox/Sources/BoxList/AddBookmark/AddBookmarkViewController.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift @@ -7,6 +7,8 @@ import UIKit +import SwiftSoup + protocol AddBookmarkViewControllerProtocol: AnyObject { func addFolderDirect(_ folder: Folder) func addBookmarkDirect(_ bookmark: Bookmark, at folderIndex: Int) @@ -20,7 +22,7 @@ final class AddBookmarkViewController: UIViewController { var selectedFolderIndex: Int? var folders = [Folder]() - private let addBookmarkView = AddBookmarkView() + let addBookmarkView = AddBookmarkView() override func loadView() { super.loadView() @@ -31,6 +33,7 @@ final class AddBookmarkViewController: UIViewController { super.viewWillAppear(animated) updateSelectedFolder() addBookmarkView.updateTextFieldsFilledState() + addBookmarkView.nameTextView.becomeFirstResponder() } override func viewDidLoad() { @@ -156,6 +159,72 @@ final class AddBookmarkViewController: UIViewController { navigationController?.pushViewController(folderListViewController, animated: true) } + private func fetchAndParseMetadata(from url: URL, completion: @escaping (Metadata) -> Void) { + URLSession.shared.dataTask(with: url) { data, response, error in + guard let data = data, error == nil else { + print("Failed to fetch data: \(String(describing: error))") + return + } + + let encodingName = (response as? HTTPURLResponse)?.textEncodingName ?? "utf-8" + let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) + + if let htmlContent = String(data: data, encoding: encoding) { + do { + let doc: Document = try SwiftSoup.parse(htmlContent) + let title: String? = try doc.title() + + let faviconSelectors = ["link[rel='shortcut icon']", "link[rel='icon']", "link[rel='apple-touch-icon']"] + var faviconUrl: String? = nil + + for selector in faviconSelectors { + if let faviconLink: Element = try doc.select(selector).first() { + if var href = try? faviconLink.attr("href"), !href.isEmpty { + if href.starts(with: "/") { + href = url.scheme! + "://" + url.host! + href + } else if !href.starts(with: "http") { + href = url.scheme! + "://" + url.host! + "/" + href + } + faviconUrl = href + break + } + } + } + + if faviconUrl == nil { + faviconUrl = url.scheme! + "://" + url.host! + "/favicon.ico" + } + + let decodedUrlString = url.absoluteString.removingPercentEncoding ?? url.absoluteString + let metadata = Metadata(title: title, faviconUrl: faviconUrl, url: decodedUrlString) + + DispatchQueue.main.async { + completion(metadata) + } + } catch { + print("Failed to parse HTML: \(error.localizedDescription)") + } + } + }.resume() + } + + private func getMetadata() { +// fetchAndParseMetadata(from: url) { metadata in +// dump(metadata) +// let encodedTitle = metadata.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" +// let encodedData = metadata.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" +// let encodedFaviconUrl = metadata.faviconUrl?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" +// let urlString = "iBox://url?title=\(encodedTitle)&data=\(encodedData)&faviconUrl=\(encodedFaviconUrl)" +// +// if let openUrl = URL(string: urlString) { +// if self.openURL(openUrl) { +// print("iBox 앱이 성공적으로 열렸습니다.") +// } else { +// print("iBox 앱을 열 수 없습니다.") +// } +// } +// } + } } extension AddBookmarkViewController: FolderListViewControllerDelegate { diff --git a/iBox/Sources/BoxList/AddBookmark/FolderListCell.swift b/iBox/Sources/AddBookmark/FolderListCell.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/FolderListCell.swift rename to iBox/Sources/AddBookmark/FolderListCell.swift diff --git a/iBox/Sources/BoxList/AddBookmark/FolderListView.swift b/iBox/Sources/AddBookmark/FolderListView.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/FolderListView.swift rename to iBox/Sources/AddBookmark/FolderListView.swift diff --git a/iBox/Sources/BoxList/AddBookmark/FolderListViewController.swift b/iBox/Sources/AddBookmark/FolderListViewController.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/FolderListViewController.swift rename to iBox/Sources/AddBookmark/FolderListViewController.swift diff --git a/iBox/Sources/Model/Metadata.swift b/iBox/Sources/Model/Metadata.swift new file mode 100644 index 0000000..8f95deb --- /dev/null +++ b/iBox/Sources/Model/Metadata.swift @@ -0,0 +1,12 @@ +// +// Metadata.swift +// iBoxShareExtension +// +// Created by 김찬희 on 2024/03/14. +// + +struct Metadata { + var title: String? + var faviconUrl: String? + var url: String? +} From 79ee850838aea9774c87bd5b02a90eb27892664f Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 14 Apr 2024 03:37:16 +0900 Subject: [PATCH 04/14] fix: consider safe area layout --- ShareExtension/Sources/ShareViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ShareExtension/Sources/ShareViewController.swift b/ShareExtension/Sources/ShareViewController.swift index 5e7b22b..5007967 100644 --- a/ShareExtension/Sources/ShareViewController.swift +++ b/ShareExtension/Sources/ShareViewController.swift @@ -47,12 +47,12 @@ class CustomShareViewController: UIViewController { private func setupLayout() { modalView.snp.makeConstraints { make in - make.top.bottom.leading.trailing.equalToSuperview() + make.edges.equalTo(view.safeAreaLayoutGuide) } backgroundView.snp.makeConstraints { make in - make.trailing.leading.equalToSuperview().inset(30) - make.center.equalToSuperview().offset(-20) + make.leading.trailing.equalToSuperview().inset(30) + make.centerY.equalToSuperview().inset(20) make.height.equalTo(140) } } From 0009301dd8f8d37e4b0ba20cea7ca05cf5050bf5 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 14 Apr 2024 03:37:28 +0900 Subject: [PATCH 05/14] style: font color black --- ShareExtension/Sources/View/ShareExtensionBackGroundView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift b/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift index d8bf165..8fd1643 100644 --- a/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift +++ b/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift @@ -71,7 +71,7 @@ class ShareExtensionBackGroundView: UIView { button.setBackgroundColor(.clear, for: .normal) button.setTitle("앱이 실행됩니다", for: .highlighted) - button.setTitleColor(.white, for: .highlighted) + button.setTitleColor(.darkGray, for: .highlighted) button.setBackgroundColor(.box2, for: .highlighted) button.setImage(UIImage(systemName: "heart.fill"), for: .highlighted) From 6670f48e5e1d08e090e4c74a5d2fd7a5d7f6b002 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 14 Apr 2024 03:38:58 +0900 Subject: [PATCH 06/14] chore: rename --- ...+Extension.swift => ShareExtension+UIbutton+Extension.swift} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ShareExtension/Sources/Extension/{UIbutton+Extension.swift => ShareExtension+UIbutton+Extension.swift} (93%) diff --git a/ShareExtension/Sources/Extension/UIbutton+Extension.swift b/ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift similarity index 93% rename from ShareExtension/Sources/Extension/UIbutton+Extension.swift rename to ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift index 385e206..466fe44 100644 --- a/ShareExtension/Sources/Extension/UIbutton+Extension.swift +++ b/ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift @@ -1,5 +1,5 @@ // -// UIbutton+Extension.swift +// ShareExtension+UIbutton+Extension.swift // iBoxShareExtension // // Created by Chan on 4/14/24. From 8904fd839d5312621915dcadf911431c9696714a Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 14 Apr 2024 03:46:25 +0900 Subject: [PATCH 07/14] fix: url error --- ShareExtension/Sources/ShareViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ShareExtension/Sources/ShareViewController.swift b/ShareExtension/Sources/ShareViewController.swift index 5007967..a0678cd 100644 --- a/ShareExtension/Sources/ShareViewController.swift +++ b/ShareExtension/Sources/ShareViewController.swift @@ -30,8 +30,8 @@ class CustomShareViewController: UIViewController { setupProperty() setupHierarchy() setupLayout() - extractSharedURL() setupModal() + extractSharedURL() } // MARK: - Setup Methods @@ -128,7 +128,7 @@ extension CustomShareViewController: ShareExtensionBackGroundViewDelegate { } func didTapOpenApp() { - guard let sharedURL = dataURL, let url = URL(string: sharedURL) else { + guard let sharedURL = dataURL else { print("Share extension error") return } @@ -141,6 +141,8 @@ extension CustomShareViewController: ShareExtensionBackGroundViewDelegate { } else { print("iBox 앱을 열 수 없습니다.") } + } else { + print("url error") } } } From b54595b2c79b7a4c58da8827946c967d55e13bf6 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 14:49:11 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=EC=83=81=EC=9C=84=20UITabBar=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B0=BE=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIViewController+Extension.swift | 21 ++++++++++++++ iBox/Sources/Shared/URLdecoder.swift | 29 ------------------- 2 files changed, 21 insertions(+), 29 deletions(-) create mode 100644 iBox/Sources/Extension/UIViewController+Extension.swift delete mode 100644 iBox/Sources/Shared/URLdecoder.swift diff --git a/iBox/Sources/Extension/UIViewController+Extension.swift b/iBox/Sources/Extension/UIViewController+Extension.swift new file mode 100644 index 0000000..db7a491 --- /dev/null +++ b/iBox/Sources/Extension/UIViewController+Extension.swift @@ -0,0 +1,21 @@ +// +// UIViewController+Extension.swift +// iBox +// +// Created by Chan on 4/16/24. +// + +import UIKit + +extension UIViewController { + func findMainTabBarController() -> UITabBarController? { + var responder: UIResponder? = self + while let nextResponder = responder?.next { + if let viewController = nextResponder as? UITabBarController { + return viewController + } + responder = nextResponder + } + return nil + } +} diff --git a/iBox/Sources/Shared/URLdecoder.swift b/iBox/Sources/Shared/URLdecoder.swift deleted file mode 100644 index 7efb229..0000000 --- a/iBox/Sources/Shared/URLdecoder.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// URLdecoder.swift -// iBoxShareExtension -// -// Created by 김찬희 on 2024/03/14. -// - -import Foundation - -class URLdecoder { - static func handleCustomURL(_ url: URL) -> (title: String?, data: String?, faviconUrl: String?) { - guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return (nil, nil, nil) } - - let title = urlComponents.queryItems?.first(where: { $0.name == "title" })?.value - let data = urlComponents.queryItems?.first(where: { $0.name == "data" })?.value - let faviconUrl = urlComponents.queryItems?.first(where: { $0.name == "faviconUrl" })?.value - - let finalTitle: String? - if let title = title, !title.isEmpty { - finalTitle = title - } else if let data = data, let dataUrl = URL(string: data) { - finalTitle = dataUrl.host ?? "No Title" - } else { - finalTitle = nil - } - - return (finalTitle, data, faviconUrl) - } -} From 25d05724c1adfc88dece907b350d278c99096c06 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 14:50:12 +0900 Subject: [PATCH 09/14] feat: metadata manager --- iBox/Sources/Shared/URLDataManager.swift | 57 ++++++++++++++++++++---- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/iBox/Sources/Shared/URLDataManager.swift b/iBox/Sources/Shared/URLDataManager.swift index 2f535ab..06fec25 100644 --- a/iBox/Sources/Shared/URLDataManager.swift +++ b/iBox/Sources/Shared/URLDataManager.swift @@ -6,6 +6,7 @@ // import UIKit +import SwiftSoup class URLDataManager { static let shared = URLDataManager() @@ -13,22 +14,61 @@ class URLDataManager { var incomingTitle: String? var incomingData: String? var incomingFaviconUrl: String? - + private init() {} - - func update(with data: (title: String?, data: String?, faviconUrl: String?)) { + + private func update(with data: (title: String?, data: String?, faviconUrl: String?)) { incomingTitle = data.title incomingData = data.data incomingFaviconUrl = data.faviconUrl } + private func parseHTML(_ html: String, _ url: URL) { + do { + let doc = try SwiftSoup.parse(html) + let title = try doc.title() + let faviconLink = try doc.select("link[rel='icon']").first()?.attr("href") + + DispatchQueue.main.async { + self.update(with: (title: title, data: url.absoluteString, faviconUrl: faviconLink)) + } + } catch { + print("Error parsing HTML: \(error)") + } + } + + private func extractDataParameter(from url: URL) -> String? { + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems else { + return nil + } + return queryItems.first { $0.name == "data" }?.value + } + + private func fetchWebsiteDetails(from url: URL) { + let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in + guard let data = data, error == nil, + let html = String(data: data, encoding: .utf8) else { + print("Error downloading HTML: \(String(describing: error))") + return + } + + self?.parseHTML(html, url) + } + task.resume() + } + func navigateToAddBookmarkView(from url: URL, in tabBarController: UITabBarController) { - guard url.scheme == "iBox" else { return } - - let urlData = URLdecoder.handleCustomURL(url) - self.update(with: urlData) - + guard url.scheme == "iBox", let urlString = extractDataParameter(from: url) else { return } + guard let url = URL(string: urlString) else { + print("Invalid URL") + return + } + + fetchWebsiteDetails(from: url) + tabBarController.selectedIndex = 0 + DispatchQueue.main.async { guard let navigationController = tabBarController.selectedViewController as? UINavigationController, let boxListViewController = navigationController.viewControllers.first as? BoxListViewController else { @@ -37,4 +77,5 @@ class URLDataManager { boxListViewController.shouldPresentModalAutomatically = true } } + } From a0312c51e619545b38893ede77eef316643a5586 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 14:50:41 +0900 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20=EA=B8=B0=EC=A1=B4=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=97=90=EC=84=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iBox/Sources/Web/WebViewController.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/iBox/Sources/Web/WebViewController.swift b/iBox/Sources/Web/WebViewController.swift index 19b2ebe..00346d6 100644 --- a/iBox/Sources/Web/WebViewController.swift +++ b/iBox/Sources/Web/WebViewController.swift @@ -48,12 +48,12 @@ extension WebViewController: WebViewDelegate { func pushAddBookMarkViewController(url: URL) { URLDataManager.shared.incomingData = url.absoluteString - let addBookmarkViewController = AddBookmarkViewController() - addBookmarkViewController.delegate = delegate + if let iBoxUrl = URL(string: "iBox://url?data=" + url.absoluteString) { + if let tabBarController = findMainTabBarController() { + URLDataManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) + } + } - let navigationController = UINavigationController(rootViewController: addBookmarkViewController) - navigationController.modalPresentationStyle = .pageSheet - self.present(navigationController, animated: true, completion: nil) } } From 899a333646af32bf198f5e0f018e40debce6a47c Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 17:11:15 +0900 Subject: [PATCH 11/14] feat: addbookmarkmanager setup bidings --- .../Sources/AddBookmark/AddBookmarkView.swift | 42 +++++++++++++++---- .../CustomLaunchScreenViewController.swift | 3 +- iBox/Sources/SceneDelegate.swift | 2 +- ...Manager.swift => AddBookmarkManager.swift} | 24 ++++++----- iBox/Sources/Web/WebViewController.swift | 4 +- 5 files changed, 52 insertions(+), 23 deletions(-) rename iBox/Sources/Shared/{URLDataManager.swift => AddBookmarkManager.swift} (81%) diff --git a/iBox/Sources/AddBookmark/AddBookmarkView.swift b/iBox/Sources/AddBookmark/AddBookmarkView.swift index 62ef8fb..aece6c2 100644 --- a/iBox/Sources/AddBookmark/AddBookmarkView.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkView.swift @@ -1,16 +1,19 @@ // -// AddBookmarkBottomSheetView.swift +// AddBookmarkView.swift // iBox // // Created by jiyeon on 1/5/24. // import UIKit +import Combine import SnapKit class AddBookmarkView: UIView { + var cancellables = Set() + var onButtonTapped: (() -> Void)? var onTextChange: ((Bool) -> Void)? @@ -19,9 +22,9 @@ class AddBookmarkView: UIView { selectedFolderLabel.text = selectedFolderName } } - + // MARK: - UI Components - + private let textFieldView: UIView = UIView().then { $0.backgroundColor = UIColor.backgroundColor $0.layer.cornerRadius = 20 @@ -106,6 +109,8 @@ class AddBookmarkView: UIView { setupProperty() setupHierarchy() setupLayout() + setupBindings() + } required init?(coder: NSCoder) { @@ -227,12 +232,33 @@ class AddBookmarkView: UIView { } } + private func setupBindings() { + AddBookmarkManager.shared.$incomingTitle + .receive(on: DispatchQueue.main) + .sink { [weak self] title in + self?.nameTextView.text = title + self?.nameTextViewPlaceHolder.isHidden = !(title?.isEmpty ?? true) + self?.clearButton.isHidden = title?.isEmpty ?? true + self?.updateTextFieldsFilledState() + } + .store(in: &cancellables) + + AddBookmarkManager.shared.$incomingData + .receive(on: DispatchQueue.main) + .sink { [weak self] url in + self?.urlTextView.text = url + self?.urlTextViewPlaceHolder.isHidden = !(url?.isEmpty ?? true) + self?.updateTextFieldsFilledState() + } + .store(in: &cancellables) + } + private func updateTextFieldWithIncomingData() { - updateTextField(textField: nameTextView, placeholder: nameTextViewPlaceHolder, withData: URLDataManager.shared.incomingTitle) - URLDataManager.shared.incomingTitle = nil + updateTextField(textField: nameTextView, placeholder: nameTextViewPlaceHolder, withData: AddBookmarkManager.shared.incomingTitle) + AddBookmarkManager.shared.incomingTitle = nil - updateTextField(textField: urlTextView, placeholder: urlTextViewPlaceHolder, withData: URLDataManager.shared.incomingData) - URLDataManager.shared.incomingData = nil + updateTextField(textField: urlTextView, placeholder: urlTextViewPlaceHolder, withData: AddBookmarkManager.shared.incomingData) + AddBookmarkManager.shared.incomingData = nil } func updateTextFieldsFilledState() { @@ -281,7 +307,7 @@ extension AddBookmarkView: UITextViewDelegate { nameTextViewPlaceHolder.isHidden = !nameTextView.text.isEmpty clearButton.isHidden = nameTextView.text.isEmpty } - + if textView == urlTextView { urlTextViewPlaceHolder.isHidden = !urlTextView.text.isEmpty } diff --git a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift b/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift index 935d34d..0e80413 100644 --- a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift +++ b/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift @@ -80,8 +80,9 @@ class CustomLaunchScreenViewController: UIViewController { if let urlContext = self.urlContext, let tabBarController = window.rootViewController as? UITabBarController { - URLDataManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) + AddBookmarkManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) } + UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: {}, completion: nil) } } diff --git a/iBox/Sources/SceneDelegate.swift b/iBox/Sources/SceneDelegate.swift index c3b0c18..42915c7 100644 --- a/iBox/Sources/SceneDelegate.swift +++ b/iBox/Sources/SceneDelegate.swift @@ -49,7 +49,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { if let urlContext = URLContexts.first, let tabBarController = window?.rootViewController as? UITabBarController { - URLDataManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) + AddBookmarkManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) } } diff --git a/iBox/Sources/Shared/URLDataManager.swift b/iBox/Sources/Shared/AddBookmarkManager.swift similarity index 81% rename from iBox/Sources/Shared/URLDataManager.swift rename to iBox/Sources/Shared/AddBookmarkManager.swift index 06fec25..68ce206 100644 --- a/iBox/Sources/Shared/URLDataManager.swift +++ b/iBox/Sources/Shared/AddBookmarkManager.swift @@ -6,21 +6,24 @@ // import UIKit + import SwiftSoup -class URLDataManager { - static let shared = URLDataManager() +class AddBookmarkManager { + static let shared = AddBookmarkManager() - var incomingTitle: String? - var incomingData: String? - var incomingFaviconUrl: String? + @Published var incomingTitle: String? + @Published var incomingData: String? + @Published var incomingFaviconUrl: String? private init() {} private func update(with data: (title: String?, data: String?, faviconUrl: String?)) { - incomingTitle = data.title - incomingData = data.data - incomingFaviconUrl = data.faviconUrl + DispatchQueue.main.async { + self.incomingTitle = data.title + self.incomingData = data.data + self.incomingFaviconUrl = data.faviconUrl + } } private func parseHTML(_ html: String, _ url: URL) { @@ -29,9 +32,8 @@ class URLDataManager { let title = try doc.title() let faviconLink = try doc.select("link[rel='icon']").first()?.attr("href") - DispatchQueue.main.async { - self.update(with: (title: title, data: url.absoluteString, faviconUrl: faviconLink)) - } + self.update(with: (title: title, data: url.absoluteString, faviconUrl: faviconLink)) + } catch { print("Error parsing HTML: \(error)") } diff --git a/iBox/Sources/Web/WebViewController.swift b/iBox/Sources/Web/WebViewController.swift index 00346d6..5f017ea 100644 --- a/iBox/Sources/Web/WebViewController.swift +++ b/iBox/Sources/Web/WebViewController.swift @@ -46,11 +46,11 @@ class WebViewController: BaseViewController, BaseViewControllerProtocol extension WebViewController: WebViewDelegate { func pushAddBookMarkViewController(url: URL) { - URLDataManager.shared.incomingData = url.absoluteString + AddBookmarkManager.shared.incomingData = url.absoluteString if let iBoxUrl = URL(string: "iBox://url?data=" + url.absoluteString) { if let tabBarController = findMainTabBarController() { - URLDataManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) + AddBookmarkManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) } } From d27e2783b1ae8f00a7b262d77e4b4a0a707579fe Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 17:11:38 +0900 Subject: [PATCH 12/14] chore: delete unnecessray lines --- .../AddBookmarkViewController.swift | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/iBox/Sources/AddBookmark/AddBookmarkViewController.swift b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift index c7e51c6..0fa2ad8 100644 --- a/iBox/Sources/AddBookmark/AddBookmarkViewController.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift @@ -7,8 +7,6 @@ import UIKit -import SwiftSoup - protocol AddBookmarkViewControllerProtocol: AnyObject { func addFolderDirect(_ folder: Folder) func addBookmarkDirect(_ bookmark: Bookmark, at folderIndex: Int) @@ -159,72 +157,6 @@ final class AddBookmarkViewController: UIViewController { navigationController?.pushViewController(folderListViewController, animated: true) } - private func fetchAndParseMetadata(from url: URL, completion: @escaping (Metadata) -> Void) { - URLSession.shared.dataTask(with: url) { data, response, error in - guard let data = data, error == nil else { - print("Failed to fetch data: \(String(describing: error))") - return - } - - let encodingName = (response as? HTTPURLResponse)?.textEncodingName ?? "utf-8" - let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) - - if let htmlContent = String(data: data, encoding: encoding) { - do { - let doc: Document = try SwiftSoup.parse(htmlContent) - let title: String? = try doc.title() - - let faviconSelectors = ["link[rel='shortcut icon']", "link[rel='icon']", "link[rel='apple-touch-icon']"] - var faviconUrl: String? = nil - - for selector in faviconSelectors { - if let faviconLink: Element = try doc.select(selector).first() { - if var href = try? faviconLink.attr("href"), !href.isEmpty { - if href.starts(with: "/") { - href = url.scheme! + "://" + url.host! + href - } else if !href.starts(with: "http") { - href = url.scheme! + "://" + url.host! + "/" + href - } - faviconUrl = href - break - } - } - } - - if faviconUrl == nil { - faviconUrl = url.scheme! + "://" + url.host! + "/favicon.ico" - } - - let decodedUrlString = url.absoluteString.removingPercentEncoding ?? url.absoluteString - let metadata = Metadata(title: title, faviconUrl: faviconUrl, url: decodedUrlString) - - DispatchQueue.main.async { - completion(metadata) - } - } catch { - print("Failed to parse HTML: \(error.localizedDescription)") - } - } - }.resume() - } - - private func getMetadata() { -// fetchAndParseMetadata(from: url) { metadata in -// dump(metadata) -// let encodedTitle = metadata.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" -// let encodedData = metadata.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" -// let encodedFaviconUrl = metadata.faviconUrl?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" -// let urlString = "iBox://url?title=\(encodedTitle)&data=\(encodedData)&faviconUrl=\(encodedFaviconUrl)" -// -// if let openUrl = URL(string: urlString) { -// if self.openURL(openUrl) { -// print("iBox 앱이 성공적으로 열렸습니다.") -// } else { -// print("iBox 앱을 열 수 없습니다.") -// } -// } -// } - } } extension AddBookmarkViewController: FolderListViewControllerDelegate { From ef111835203cde25591f6237564f66c0145357cd Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 17:27:00 +0900 Subject: [PATCH 13/14] chore: delete unused function --- .../Sources/AddBookmark/AddBookmarkView.swift | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/iBox/Sources/AddBookmark/AddBookmarkView.swift b/iBox/Sources/AddBookmark/AddBookmarkView.swift index aece6c2..ad54c0f 100644 --- a/iBox/Sources/AddBookmark/AddBookmarkView.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkView.swift @@ -110,18 +110,21 @@ class AddBookmarkView: UIView { setupHierarchy() setupLayout() setupBindings() - } required init?(coder: NSCoder) { super.init(coder: coder) } + deinit { + AddBookmarkManager.shared.incomingTitle = nil + AddBookmarkManager.shared.incomingData = nil + } + // MARK: - Setup Methods private func setupProperty() { backgroundColor = .systemGroupedBackground - updateTextFieldWithIncomingData() clearButton.addTarget(self, action: #selector(clearTextView), for: .touchUpInside) button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) nameTextView.delegate = self @@ -216,22 +219,6 @@ class AddBookmarkView: UIView { } - private func updateTextField(textField: UITextView, placeholder: UILabel, withData data: String?) { - if let data = data, !data.isEmpty { - textField.text = data - placeholder.isHidden = true - if textField == nameTextView { - clearButton.isHidden = false - } - } else { - textField.text = "" - placeholder.isHidden = false - if textField == nameTextView { - clearButton.isHidden = true - } - } - } - private func setupBindings() { AddBookmarkManager.shared.$incomingTitle .receive(on: DispatchQueue.main) @@ -253,14 +240,6 @@ class AddBookmarkView: UIView { .store(in: &cancellables) } - private func updateTextFieldWithIncomingData() { - updateTextField(textField: nameTextView, placeholder: nameTextViewPlaceHolder, withData: AddBookmarkManager.shared.incomingTitle) - AddBookmarkManager.shared.incomingTitle = nil - - updateTextField(textField: urlTextView, placeholder: urlTextViewPlaceHolder, withData: AddBookmarkManager.shared.incomingData) - AddBookmarkManager.shared.incomingData = nil - } - func updateTextFieldsFilledState() { let isBothTextViewsFilled = !(nameTextView.text?.isEmpty ?? true) && !(urlTextView.text?.isEmpty ?? true) onTextChange?(isBothTextViewsFilled) From 26aab00541b5ea528c6436d56278faf3c3a8662a Mon Sep 17 00:00:00 2001 From: chanhihi Date: Tue, 16 Apr 2024 17:50:36 +0900 Subject: [PATCH 14/14] =?UTF-8?q?fix:=20addbookmarkview=EA=B0=80=20?= =?UTF-8?q?=EB=96=A0=EC=9E=88=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20dismiss=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iBox/Sources/BoxList/BoxListViewController.swift | 2 +- .../Extension/UIViewController+Extension.swift | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/iBox/Sources/BoxList/BoxListViewController.swift b/iBox/Sources/BoxList/BoxListViewController.swift index 57aa367..74d81cb 100644 --- a/iBox/Sources/BoxList/BoxListViewController.swift +++ b/iBox/Sources/BoxList/BoxListViewController.swift @@ -13,7 +13,7 @@ class BoxListViewController: BaseViewController, BaseViewController didSet { if shouldPresentModalAutomatically { // shouldPresentModalAutomatically가 true로 설정될 때 함수 호출 - dismiss(animated: false) { + if findAddBookmarkView() == nil { self.addButtonTapped() } // 함수 호출 후 shouldPresentModalAutomatically를 false로 설정 diff --git a/iBox/Sources/Extension/UIViewController+Extension.swift b/iBox/Sources/Extension/UIViewController+Extension.swift index db7a491..5277a9d 100644 --- a/iBox/Sources/Extension/UIViewController+Extension.swift +++ b/iBox/Sources/Extension/UIViewController+Extension.swift @@ -18,4 +18,17 @@ extension UIViewController { } return nil } + + func findAddBookmarkView() -> Bool? { + var responder: UIResponder? = self + while let nextResponder = responder?.next { + if let viewController = nextResponder as? AddBookmarkView { + return true + } + responder = nextResponder + } + return nil + } + + }