diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index ce0bd2d05..ac7bf1c77 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -872,6 +872,9 @@ ECF57A5B250925AC004BBF9D /* TextFieldTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = ECF57A59250925AC004BBF9D /* TextFieldTableViewCell.xib */; }; ECF57A5E2509265A004BBF9D /* InsettedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF57A5D2509265A004BBF9D /* InsettedTextField.swift */; }; ECF57A6225093B04004BBF9D /* SectionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF57A6025093B04004BBF9D /* SectionHeaderView.m */; }; + ECF57A85250A64F0004BBF9D /* PlaceholderedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF57A84250A64F0004BBF9D /* PlaceholderedTextView.swift */; }; + ECF57A87250A6872004BBF9D /* TextViewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF57A86250A6872004BBF9D /* TextViewTableViewCell.swift */; }; + ECF57A89250A687B004BBF9D /* TextViewTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = ECF57A88250A687B004BBF9D /* TextViewTableViewCell.xib */; }; F05927C91FDED836009F2A68 /* MXGroup+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F05927C71FDED835009F2A68 /* MXGroup+Riot.m */; }; F083BD1E1E7009ED00A9B29C /* LegacyAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BB0D1E7009EC00A9B29C /* LegacyAppDelegate.m */; }; F083BDE61E7009ED00A9B29C /* busy.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDB1E7009EC00A9B29C /* busy.mp3 */; }; @@ -2022,6 +2025,9 @@ ECF57A5D2509265A004BBF9D /* InsettedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsettedTextField.swift; sourceTree = ""; }; ECF57A6025093B04004BBF9D /* SectionHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionHeaderView.m; sourceTree = ""; }; ECF57A6125093B04004BBF9D /* SectionHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SectionHeaderView.h; sourceTree = ""; }; + ECF57A84250A64F0004BBF9D /* PlaceholderedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderedTextView.swift; sourceTree = ""; }; + ECF57A86250A6872004BBF9D /* TextViewTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewTableViewCell.swift; sourceTree = ""; }; + ECF57A88250A687B004BBF9D /* TextViewTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TextViewTableViewCell.xib; sourceTree = ""; }; F05927C71FDED835009F2A68 /* MXGroup+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXGroup+Riot.m"; sourceTree = ""; }; F05927C81FDED835009F2A68 /* MXGroup+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXGroup+Riot.h"; sourceTree = ""; }; F083BB031E7005FD00A9B29C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -4196,6 +4202,8 @@ B1B5592920EF7A5D00210D55 /* TableViewCellWithButton.xib */, ECF57A58250925AC004BBF9D /* TextFieldTableViewCell.swift */, ECF57A59250925AC004BBF9D /* TextFieldTableViewCell.xib */, + ECF57A86250A6872004BBF9D /* TextViewTableViewCell.swift */, + ECF57A88250A687B004BBF9D /* TextViewTableViewCell.xib */, ); path = Cells; sourceTree = ""; @@ -4950,6 +4958,7 @@ isa = PBXGroup; children = ( ECF57A5D2509265A004BBF9D /* InsettedTextField.swift */, + ECF57A84250A64F0004BBF9D /* PlaceholderedTextView.swift */, ); path = Views; sourceTree = ""; @@ -5469,6 +5478,7 @@ B1550FCA2420E8F500CE097B /* QRCodeReaderViewController.storyboard in Resources */, B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */, F083BE011E7009ED00A9B29C /* third_party_licenses.html in Resources */, + ECF57A89250A687B004BBF9D /* TextViewTableViewCell.xib in Resources */, EC3B066A24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.xib in Resources */, B1098BFC21ECFE65000DDA48 /* PasswordStrengthView.xib in Resources */, B1B5573720EE6C4D00210D55 /* GroupParticipantsViewController.xib in Resources */, @@ -6273,6 +6283,7 @@ ECF57A4925090C23004BBF9D /* EnterNewRoomDetailsViewState.swift in Sources */, B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */, B1098BE121ECE09F000DDA48 /* Images.swift in Sources */, + ECF57A85250A64F0004BBF9D /* PlaceholderedTextView.swift in Sources */, B1B4E9C424D47207004D5C33 /* BubbleReactionsViewModelBuilder.swift in Sources */, EC85D72A2477DCF2002C44C9 /* KeyVerificationManuallyVerifyViewModel.swift in Sources */, B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */, @@ -6327,6 +6338,7 @@ B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */, B1C543B223A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift in Sources */, 323AB947232BD74600C1451F /* AuthFallBackViewController.m in Sources */, + ECF57A87250A6872004BBF9D /* TextViewTableViewCell.swift in Sources */, B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */, B1CE83DF2422817200D07506 /* KeyVerificationVerifyBySASCoordinatorType.swift in Sources */, B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */, diff --git a/Riot/Modules/Common/Cells/TextViewTableViewCell.swift b/Riot/Modules/Common/Cells/TextViewTableViewCell.swift new file mode 100644 index 000000000..830e6cf69 --- /dev/null +++ b/Riot/Modules/Common/Cells/TextViewTableViewCell.swift @@ -0,0 +1,37 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +/// Table view cell with only a text view spanning the whole content view, insets can be configured via `textView.textContainerInset` +class TextViewTableViewCell: UITableViewCell { + + @IBOutlet weak var textView: PlaceholderedTextView! + +} + +extension TextViewTableViewCell: NibReusable {} + +extension TextViewTableViewCell: Themable { + + func update(theme: Theme) { + textView.textColor = theme.textPrimaryColor + textView.tintColor = theme.tintColor + textView.placeholderColor = theme.placeholderTextColor + } + +} diff --git a/Riot/Modules/Common/Cells/TextViewTableViewCell.xib b/Riot/Modules/Common/Cells/TextViewTableViewCell.xib new file mode 100644 index 000000000..eb1540020 --- /dev/null +++ b/Riot/Modules/Common/Cells/TextViewTableViewCell.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Common/Views/PlaceholderedTextView.swift b/Riot/Modules/Common/Views/PlaceholderedTextView.swift new file mode 100644 index 000000000..dd4eb9d71 --- /dev/null +++ b/Riot/Modules/Common/Views/PlaceholderedTextView.swift @@ -0,0 +1,119 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class PlaceholderedTextView: UITextView { + + private var kvoLineFragmentPadding: NSKeyValueObservation? + private lazy var placeholderTextView: UITextView = { + let view = UITextView() + view.contentInset = self.contentInset + view.textContainerInset = self.textContainerInset + view.textContainer.lineFragmentPadding = self.textContainer.lineFragmentPadding + view.scrollsToTop = false + view.showsVerticalScrollIndicator = false + view.showsHorizontalScrollIndicator = false + view.isEditable = false + view.isSelectable = false + view.isScrollEnabled = false + view.textColor = self.placeholderColor + if let placeholder = self.placeholder { + view.text = placeholder + } else if let attributedPlaceholder = self.attributedPlaceholder { + view.attributedText = attributedPlaceholder + } + view.backgroundColor = .clear + view.tintColor = .clear + self.addSubview(view) + self.sendSubviewToBack(view) + return view + }() + + var placeholder: String? { + didSet { + placeholderTextView.text = placeholder + } + } + var attributedPlaceholder: NSAttributedString? { + didSet { + placeholderTextView.attributedText = attributedPlaceholder + } + } + var placeholderColor: UIColor = .lightGray { + didSet { + placeholderTextView.textColor = placeholderColor + } + } + + override var font: UIFont? { + didSet { + placeholderTextView.font = font + } + } + + override var contentInset: UIEdgeInsets { + didSet { + placeholderTextView.contentInset = contentInset + } + } + + override var textContainerInset: UIEdgeInsets { + didSet { + placeholderTextView.textContainerInset = textContainerInset + } + } + + override func layoutSubviews() { + super.layoutSubviews() + placeholderTextView.frame = bounds + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + private func setup() { + NotificationCenter.default.addObserver(self, selector: #selector(textViewTextChanged(_:)), name: UITextView.textDidChangeNotification, object: self) + kvoLineFragmentPadding = observe(\.textContainer.lineFragmentPadding, options: [.new]) { [weak self] (_, change) in + guard let self = self else { return } + let newValue = change.newValue ?? 0 + self.placeholderTextView.textContainer.lineFragmentPadding = newValue + } + } + + override var text: String! { + didSet { + super.text = text + updatePlaceholderVisibility() + } + } + + @objc func textViewTextChanged(_ sender: UITextView) { + updatePlaceholderVisibility() + } + + private func updatePlaceholderVisibility() { + placeholderTextView.isHidden = text.count > 0 + } + +} diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift index a8e032c32..b13fa6315 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift @@ -248,6 +248,7 @@ final class EnterNewRoomDetailsViewController: UIViewController { mainTableView.register(cellType: MXKTableViewCellWithLabelAndSwitch.self) mainTableView.register(cellType: MXKTableViewCellWithTextView.self) mainTableView.register(cellType: TextFieldTableViewCell.self) + mainTableView.register(cellType: TextViewTableViewCell.self) // mainTableView.register(headerFooterViewType: TableViewHeaderFooterView.self) mainTableView.sectionHeaderHeight = UITableView.automaticDimension mainTableView.estimatedSectionHeaderHeight = 50 @@ -348,20 +349,18 @@ extension EnterNewRoomDetailsViewController: UITableViewDataSource { return cell case .textView(let tag, let placeholder, let delegate): - let cell: MXKTableViewCellWithTextView = tableView.dequeueReusableCell(for: indexPath) - cell.mxkTextView.tag = tag - cell.mxkTextViewTopConstraint.constant = 8 - cell.mxkTextViewLeadingConstraint.constant = 16 - cell.mxkTextViewBottomConstraint.constant = 8 - cell.mxkTextViewTrailingConstraint.constant = 16 - cell.mxkTextView.textContainerInset = .zero - cell.mxkTextView.contentInset = .zero - cell.mxkTextView.textContainer.lineFragmentPadding = 0 - cell.mxkTextView.font = .systemFont(ofSize: 17) - cell.mxkTextView.text = row.text - cell.mxkTextView.isEditable = true - cell.mxkTextView.isScrollEnabled = false - cell.mxkTextView.delegate = delegate + let cell: TextViewTableViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.textView.tag = tag + cell.textView.textContainer.lineFragmentPadding = 0 + cell.textView.contentInset = .zero + cell.textView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) + cell.textView.placeholder = placeholder + cell.textView.font = .systemFont(ofSize: 17) + cell.textView.text = row.text + cell.textView.isEditable = true + cell.textView.isScrollEnabled = false + cell.textView.delegate = delegate + cell.textView.backgroundColor = .clear cell.update(theme: theme) return cell