Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat [#177] 하이퍼링크 기능 구현 #179

Merged
merged 7 commits into from
Apr 24, 2024
4 changes: 4 additions & 0 deletions DontBe-iOS/DontBe-iOS/Global/Extension/UIColor+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ extension UIColor {
return UIColor(hex: "#0E0E0E")
}

static var donLink: UIColor {
return UIColor(hex: "3479FF")
}

static var donGray1: UIColor {
return UIColor(hex: "#F6F6F7")
}
Expand Down
2 changes: 2 additions & 0 deletions DontBe-iOS/DontBe-iOS/Global/Literals/ImageLiterals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ enum ImageLiterals {
enum Write {
static var imgWritingRestriction: UIImage { .load(name: "img_writing_restriction") }
static var imgBanText: UIImage { .load(name: "img_ban_text") }
static var btnLink: UIImage { .load(name: "btn_link") }
static var btnCloseLink: UIImage { .load(name: "btn_close_link") }
}

enum Toast {
Expand Down
3 changes: 3 additions & 0 deletions DontBe-iOS/DontBe-iOS/Global/Literals/StringLiterals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ enum StringLiterals {
static let writePopupContentLabel = "작성 중인 글을 삭제하시겠어요?"
static let writePopupCancleButtonTitle = "취소"
static let writePopupConfirmButtonTitle = "삭제"
static let writeLinkPlaceholder = "URL"
static let writeOnlyOneLink = "링크는 한 개까지만 삽입할 수 있어요."
static let writeErrorLink = "아직 링크로 인식되지 않아요. 다시 확인해 주세요."
}

enum Login {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "btn_close_link.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "btn_link.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions DontBe-iOS/DontBe-iOS/Presentation/Helpers/CopyableLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,24 @@ class CopyableLabel: UILabel {
return (action == #selector(copy(_:)))
}
}

extension UILabel {
func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
guard let attributedString = attributedText else { return NSNotFound }
guard bounds.contains(point) else { return NSNotFound }

let textStorage = NSTextStorage(attributedString: attributedString)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)

let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0
textContainer.maximumNumberOfLines = numberOfLines
textContainer.lineBreakMode = lineBreakMode

layoutManager.addTextContainer(textContainer)

let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return index
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,49 @@ final class HomeCollectionViewCell: UICollectionViewCell, UICollectionViewRegist
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(with postText: String) {
contentTextLabel.attributedText = attributedString(for: postText)

// 탭 제스처 추가
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
contentTextLabel.isUserInteractionEnabled = true
contentTextLabel.addGestureRecognizer(tapGesture)
}

// URL을 하이퍼링크로 바꾸는 함수
private func attributedString(for text: String) -> NSAttributedString {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return NSAttributedString(string: text)
}

let attributedString = NSMutableAttributedString(string: text)
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))

for match in matches {
guard let range = Range(match.range, in: text) else { continue }
let url = text[range]
attributedString.addAttribute(.link, value: url, range: NSRange(range, in: text))
}

return attributedString
}

// 탭 제스처 처리 함수
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
guard let attributedText = contentTextLabel.attributedText else { return }

let location = gesture.location(in: contentTextLabel)
let index = contentTextLabel.indexOfAttributedTextCharacterAtPoint(point: location)

attributedText.enumerateAttribute(.link, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in
if let url = value as? String, NSLocationInRange(index, range) {
if let url = URL(string: url) {
UIApplication.shared.open(url)
}
}
}
}
}

// MARK: - Extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,8 @@ extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelega
cell.isLiked = self.homeViewModel.postDatas[indexPath.row].isLiked
cell.likeButton.setImage(cell.isLiked ? ImageLiterals.Posting.btnFavoriteActive : ImageLiterals.Posting.btnFavoriteInActive, for: .normal)

cell.configure(with: cell.contentTextLabel.text ?? "")

// 내가 투명도를 누른 유저인 경우 -85% 적용
if self.homeViewModel.postDatas[indexPath.row].isGhost {
cell.grayView.alpha = 0.85
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ extension MyPageCommentViewController: UICollectionViewDataSource, UICollectionV
cell.commentNumLabel.text = "\(commentDatas[indexPath.row].commentLikedNumber)"
cell.profileImageView.load(url: "\(commentDatas[indexPath.row].memberProfileUrl)")

cell.configure(with: cell.contentTextLabel.text ?? "")

cell.likeButton.setImage(commentDatas[indexPath.row].isLiked ? ImageLiterals.Posting.btnFavoriteActive : ImageLiterals.Posting.btnFavoriteInActive, for: .normal)
cell.isLiked = commentDatas[indexPath.row].isLiked

Expand All @@ -338,7 +340,8 @@ extension MyPageCommentViewController: UICollectionViewDataSource, UICollectionV

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let contentId = commentDatas[indexPath.row].contentId
NotificationCenter.default.post(name: MyPageContentViewController.pushViewController, object: nil, userInfo: ["contentId": contentId])
let profileImageURL = commentDatas[indexPath.row].memberProfileUrl
NotificationCenter.default.post(name: MyPageContentViewController.pushViewController, object: nil, userInfo: ["contentId": contentId, "profileImageURL": profileImageURL])
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ extension MyPageContentViewController: UICollectionViewDataSource, UICollectionV
cell.likeButton.setImage(contentDatas[indexPath.row].isLiked ? ImageLiterals.Posting.btnFavoriteActive : ImageLiterals.Posting.btnFavoriteInActive, for: .normal)
cell.isLiked = contentDatas[indexPath.row].isLiked

cell.configure(with: cell.contentTextLabel.text ?? "")

// 내가 투명도를 누른 유저인 경우 -85% 적용
if contentDatas[indexPath.row].isGhost {
cell.grayView.alpha = 0.85
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,48 @@ final class PostReplyCollectionViewCell: UICollectionViewCell, UICollectionViewR
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(with postText: String) {
contentTextLabel.attributedText = attributedString(for: postText)

// 탭 제스처 추가
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
contentTextLabel.isUserInteractionEnabled = true
contentTextLabel.addGestureRecognizer(tapGesture)
}

// URL을 하이퍼링크로 바꾸는 함수
private func attributedString(for text: String) -> NSAttributedString {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return NSAttributedString(string: text)
}

let attributedString = NSMutableAttributedString(string: text)
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))

for match in matches {
guard let range = Range(match.range, in: text) else { continue }
let url = text[range]
attributedString.addAttribute(.link, value: url, range: NSRange(range, in: text))
}

return attributedString
}

// 탭 제스처 처리 함수
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
guard let attributedText = contentTextLabel.attributedText else { return }
let location = gesture.location(in: contentTextLabel)
let index = contentTextLabel.indexOfAttributedTextCharacterAtPoint(point: location)

attributedText.enumerateAttribute(.link, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in
if let url = value as? String, NSLocationInRange(index, range) {
if let url = URL(string: url) {
UIApplication.shared.open(url)
}
}
}
}
}

// MARK: - Extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,19 @@ final class PostDetailViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.getAPI()
}
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

refreshControl.beginRefreshing()

self.navigationItem.hidesBackButton = true
self.navigationItem.title = StringLiterals.Post.navigationTitleLabel
self.navigationController?.navigationBar.isHidden = false
Expand Down Expand Up @@ -804,6 +814,9 @@ extension PostDetailViewController: UICollectionViewDataSource, UICollectionView
self.commentId = viewModel.postReplyDatas[indexPath.row].commentId
cell.likeButton.setImage(viewModel.postReplyDatas[indexPath.row].isLiked ? ImageLiterals.Posting.btnFavoriteActive : ImageLiterals.Posting.btnFavoriteInActive, for: .normal)
cell.isLiked = self.viewModel.postReplyDatas[indexPath.row].isLiked

cell.configure(with: cell.contentTextLabel.text ?? "")

// 내가 투명도를 누른 유저인 경우 -85% 적용
if self.viewModel.postReplyDatas[indexPath.row].isGhost {
cell.grayView.alpha = 0.85
Expand Down Expand Up @@ -851,6 +864,8 @@ extension PostDetailViewController: UICollectionViewDataSource, UICollectionView
header.ghostButton.addTarget(self, action: #selector(transparentShowPopupButton), for: .touchUpInside)
header.profileImageView.load(url: self.userProfileURL)

header.configure(with: header.contentTextLabel.text ?? "")

DispatchQueue.main.async {
self.postViewHeight = Int(header.PostbackgroundUIView.frame.height)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ final class PostDetailCollectionHeaderView: UICollectionReusableView {
let contentTextLabel: CopyableLabel = {
let label = CopyableLabel()
label.textColor = .donBlack
label.text = ""
label.lineBreakMode = .byCharWrapping
label.font = .font(.body4)
label.numberOfLines = 0
Expand Down Expand Up @@ -175,6 +174,48 @@ final class PostDetailCollectionHeaderView: UICollectionReusableView {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(with postText: String) {
contentTextLabel.attributedText = attributedString(for: postText)

// 탭 제스처 추가
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
contentTextLabel.isUserInteractionEnabled = true
contentTextLabel.addGestureRecognizer(tapGesture)
}

// URL을 하이퍼링크로 바꾸는 함수
private func attributedString(for text: String) -> NSAttributedString {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return NSAttributedString(string: text)
}

let attributedString = NSMutableAttributedString(string: text)
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))

for match in matches {
guard let range = Range(match.range, in: text) else { continue }
let url = text[range]
attributedString.addAttribute(.link, value: url, range: NSRange(range, in: text))
}

return attributedString
}

// 탭 제스처 처리 함수
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
guard let attributedText = contentTextLabel.attributedText else { return }
let location = gesture.location(in: contentTextLabel)
let index = contentTextLabel.indexOfAttributedTextCharacterAtPoint(point: location)

attributedText.enumerateAttribute(.link, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in
if let url = value as? String, NSLocationInRange(index, range) {
if let url = URL(string: url) {
UIApplication.shared.open(url)
}
}
}
}
}

// MARK: - Extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class WriteViewController: UIViewController {
private var transparency: Int = 0

private lazy var postButtonTapped = rootView.writeTextView.postButton.publisher(for: .touchUpInside).map { _ in
return self.rootView.writeTextView.contentTextView.text ?? ""
return self.rootView.writeTextView.contentTextView.text + "\n" + self.rootView.writeTextView.linkTextView.text ?? ""
}.eraseToAnyPublisher()

// MARK: - UI Components
Expand Down Expand Up @@ -143,7 +143,7 @@ extension WriteViewController {
@objc
private func cancleNavigationBarButtonTapped() {
// 텍스트가 비어있는 경우 POP
if self.rootView.writeTextView.contentTextView.text == "" {
if self.rootView.writeTextView.contentTextView.text == "" && self.rootView.writeTextView.linkTextView.text == "" {
popupNavigation()
} else {
self.rootView.writeCanclePopupView.alpha = 1
Expand Down
Loading