How to make `attributedReadMoreText` in `ReadMoreTextView` library not selectable - swift - swift

I am a new iOS programming. Now i am creating a sample app which display text using ReadMoreTextView library. My content may contain many lines but by using this library i can maximumNumberOfLines to display how many lines of content should be displayed. I implement those content in cell of UITableView and i have problem is that, when i use label.attributedReadMoreText = NSAttributedString(string: "...") then end of content will display ... and when i click on it and then whole content will be display so, my question is that: How to not letting user click on that ... because i want user to click on cell then i will show another view and display whole content there?
How can i achieve something like this? Thank in advance.
This is how i set UITextView
lazy var categoryShortDetailLabel: ReadMoreTextView = {
let label = ReadMoreTextView()
label.font = UIFont(name: "SFCompactText-Regular", size: 16)
label.textColor = .black
label.isEditable = false
label.isSelectable = false
label.maximumNumberOfLines = 3
label.shouldTrim = true
label.attributedReadMoreText = NSAttributedString(string: "...")
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()

Looking at the code here, I found that, the ReadMoreTextView is meant to provide you the feature like, ReadMore and ReadLess for the larger texts in textView.
However your requirement is to stop that functionality. Now, if you take a look at the code here, you will get the idea, that the function shoreMoreText and it's a private function so, can't override it. and this function is expanding the texts and setting the numberOfLines to zero. so, what you can do is, comment the code within and return from function to stop doing the action. Also as the ReadMoreTextView is Licensed as MIT(Read licence here) so, it's okay to modify the code.
private func showMoreText() {
return
/*if let readLessText = readLessText, text.hasSuffix(readLessText) { return }
shouldTrim = false
textContainer.maximumNumberOfLines = 0
if let originalAttributedText = _originalAttributedText?.mutableCopy() as? NSMutableAttributedString {
attributedText = _originalAttributedText
let range = NSRange(location: 0, length: text.count)
if let attributedReadLessText = attributedReadLessText {
originalAttributedText.append(attributedReadLessText)
}
textStorage.replaceCharacters(in: range, with: originalAttributedText)
}
invalidateIntrinsicContentSize()
invokeOnSizeChangeIfNeeded()*/
}
Try and share your results.
Hope it helps!

Related

How do I iterate through storyboard elements without having to write out everything?

I have the situation where I need to change the text label based on various conditions, but I cannot figure out how to iterate through labels that are created within the Storyboard without having to write it all out such as below. In the below example, I am first checking if the array item exists, and if so, changing the label text and color. If not, I'd like just a default setting of blank text and the UIColor black. The labels were added to an XIB cell.
if let item1 = currentObjective.items[safe: 0] {
cell.item1Label.text = item1.title
cell.item1Label?.textColor = returnColor(item: item1)
} else {
cell.item1Label.text = ""
cell.item1Label?.textColor = UIColor.black
}
if let item2 = currentObjective.items[safe: 1] {
cell.item2Label.text = item2.title
cell.item2Label?.textColor = returnColor(item: item2)
} else {
cell.item2Label.text = ""
cell.item2Label?.textColor = UIColor.black
}
if let item3 = currentObjective.items[safe: 2] {
cell.item3Label.text = item3.title
cell.item3Label?.textColor = returnColor(item: item3)
} else {
cell.item3Label.text = ""
cell.item3Label?.textColor = UIColor.black
}
Edit: I have been asked to show the structure of the storyboard. Please see below. These are labels placed on a XIB file one by one via drag and drop.
These are all added to the swift file via IBOutlet:
Assuming the title label is a sibling of the item labels, you can enumerate the array of all the item labels,
let itemLabels = [
cell.item1Label!,
cell.item2Label!,
cell.item3Label!,
cell.item4Label!,
]
for (i, label) in itemLabels.enumerated() {
if let item = currentObjective.items[safe: i] {
label.text = item.title
label.textColor = returnColor(item: item)
} else {
label.text = ""
label.textColor = UIColor.black
}
}
Alternatively, you can also put the four labels as subviews of another view (perhaps a UIStackView) in the storyboard, so that the hierarchy becomes:
ObjectiveCell
UIStackView
item1Label
item2Label
item3Label
item4Label
titleLabel
Then, add an outlet for the stack view. This way, you can use cell.stackView.arrangedSubviews, instead of writing out the itemLabels array.
If you want to go one step further, don't use a fixed number of item labels! Add them dynamically to the stack view instead, based on currentObjective.items.
// remove all the existing items first (I'm guessing you're doing this in cellForRowAt or something like that)
cell.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
for item in currentObjective.items {
let label = UILabel()
label.text = item.title
label.textColor = returnColor(item: item)
cell.stackView.addArrangedSubview(label)
}

How to add "read more" at end of the 3rd line in UILabel in Swift

I need to add read more at the end of the 3rd line in label.
like this i need:
Note: i have tried this answer Add "...Read More" to the end of UILabel but here read more action is not working and expand collapse also not working.. so tried like below
Actually i need to show description in label.. here if the description if more then 3 lines then i need to show read more at end of the 3rd line and need to expand the label with remaining text..
code: with this code i can expand the label if more than 3 lines but i need to show read more at end of the third line
override func viewDidLoad() {
super.viewDidLoad()
despLbl.text = publicProGenInfo?.result?.user?.description
despLbl.numberOfLines = 3//despLbl.maxNumberOfLines//3
let tap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.labelAction(gesture:)))
despLbl.addGestureRecognizer(tap)
despLbl.isUserInteractionEnabled = true
tap.delegate = self
}
#objc func labelAction(gesture: UITapGestureRecognizer)
{
if despLbl.numberOfLines == 3 {
despLbl.numberOfLines = despLbl.maxNumberOfLines
} else {
despLbl.numberOfLines = 3
}
}
o/p: getting like this.. here after 3rd line how to add read more text... please do guide
ReadMoreTextView is a good option for this purpose, with a few lines of code you can achieve this behavior.
#IBOutlet weak var aboutMessageTextView: ReadMoreTextView!
aboutMessageTextView.text = "your text"
let readMoreTextAttributes: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.foregroundColor: UIColor.systemBlue,
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14)
]
aboutMessageTextView.attributedReadMoreText = NSAttributedString(string: " ... Read more", attributes: readMoreTextAttributes)
aboutMessageTextView.maximumNumberOfLines = 3
aboutMessageTextView.shouldTrim = true

tvOS: Anyway to display a subtitle outside of the AVPlayer?

So the scenario is that there is a view where the user can enable/disable subtitles in an app I'm helping to develop.
On that view there is a sample text saying "This is what captions look like", and at the moment it's just a basic, unstyled UILabel. Ideally I would like it to be styled in a similar manner to how the user has customized their captions in the System Settings.
Is this possible in any way? I've envisioned two possible method:
Create an AVPlayer instance and a .vtt file with the text, load it into the view and pause the player. I'm not sure this is possible with a sample video (and it would somehow have to be transparent as there is an image behind the sample sub text).
Somehow get all the styling (font, size, background color, etc) the user has set for their subtitle and create an attributed string to match that
Method 2 seems like the most feasible way, but I don't know if we have access to those settings in code.
So I figured it out! It basically makes use a combination of the Media Accessibility API, which allows you to get the values the user has chosen for their captions/subtitle settings, Attributed Strings, and a subclass UILabel (although this could maybe be substituted with a UITextView as that will allow you to set it's UIEdgeInsets natively)
So, first, the subclass is to allow the UILabel to be inset. This is because captions can have a background color AND a text highlight color and without the inset, the text highlight is all you see. So the function the subclass is simple:
class InsetUILabel: UILabel {
override func drawTextInRect(rect: CGRect) {
let inset: CGFloat = 15
let insets: UIEdgeInsets = UIEdgeInsets(top: inset, left: inset/2, bottom: inset, right: inset/2)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}
And for generating the actual label. This uses a label called textSample, but you can obviously make it a little more general.
import MediaAccessibility
func styleLabel(sampleText: String) {
let domain = MACaptionAppearanceDomain.User
// Background styling
let backgroundColor = UIColor(CGColor: MACaptionAppearanceCopyWindowColor(domain, nil).takeRetainedValue())
let backgroundOpacity = MACaptionAppearanceGetWindowOpacity(domain, nil)
textSample.layer.backgroundColor = backgroundColor.colorWithAlphaComponent(backgroundOpacity).CGColor
textSample.layer.cornerRadius = MACaptionAppearanceGetWindowRoundedCornerRadius(domain, nil)
// Text styling
var textAttributes = [String:AnyObject]()
let fontDescriptor = MACaptionAppearanceCopyFontDescriptorForStyle(domain, nil, MACaptionAppearanceFontStyle.Default).takeRetainedValue()
let fontName = CTFontDescriptorCopyAttribute(fontDescriptor, "NSFontNameAttribute") as! String
let fontColor = UIColor(CGColor: MACaptionAppearanceCopyForegroundColor(domain, nil).takeRetainedValue())
let fontOpacity = MACaptionAppearanceGetForegroundOpacity(domain, nil)
let textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(domain, nil)
let textHighlightColor = UIColor(CGColor: MACaptionAppearanceCopyBackgroundColor(domain, nil).takeRetainedValue())
let textHighlightOpacity = MACaptionAppearanceGetBackgroundOpacity(domain, nil)
let textEdgeShadow = NSShadow()
textEdgeShadow.shadowColor = UIColor.blackColor()
let shortShadowOffset: CGFloat = 1.5
let shadowOffset: CGFloat = 3.5
switch(textEdgeStyle) {
case .None:
textEdgeShadow.shadowColor = UIColor.clearColor()
case .DropShadow:
textEdgeShadow.shadowOffset = CGSize(width: -shortShadowOffset, height: shortShadowOffset)
textEdgeShadow.shadowBlurRadius = 6
case .Raised:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Depressed:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: -shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Uniform:
textEdgeShadow.shadowColor = UIColor.clearColor()
textAttributes[NSStrokeColorAttributeName] = UIColor.blackColor()
textAttributes[NSStrokeWidthAttributeName] = -2.0
default:
break
}
textAttributes[NSFontAttributeName] = UIFont(name: fontName, size: (textSample.font?.pointSize)!)
textAttributes[NSForegroundColorAttributeName] = fontColor.colorWithAlphaComponent(fontOpacity)
textAttributes[NSShadowAttributeName] = textEdgeShadow
textAttributes[NSBackgroundColorAttributeName] = textHighlightColor.colorWithAlphaComponent(textHighlightOpacity)
textSample.attributedText = NSAttributedString(string: sampleText, attributes: textAttributes)
}
Now the text highlight section makes use of shadows, with values I think look pretty good, but you might want to tweak them a tiny bit. Hope this helps!

NSAttributedString: image not showing up in UILabel

For some reason, the image does not display. I have dropped test.png and test#2x.png both under Image.xcassets/ I also dropped it within the project. None of that helped. Any assistance would be greatly appreciated.
var testView: UILabel = UILabel(frame: CGRectMake(20, 20, self.view.frame.width-40, self.view.frame.height-40))
var a = "Testing..."
var attachment = NSTextAttachment()
//This is the image that I would like to add
// I have dropped test.png and test#2x.png under Image.xcassets
attachment.image = UIImage(named: "test")
var attachmentString = NSAttributedString(attachment: attachment)
let attrsB = NSAttributedString(
string: "2nd line..Testing testing",
attributes: NSDictionary(
object: UIFont(name: "HelveticaNeue", size: 18.0)!,
forKey: NSFontAttributeName) as [NSObject : AnyObject])
a.appendAttributedString(attrsB)
let attributedTextToDisplay = NSMutableAttributedString(attributedString: a)
attributedTextToDisplay.appendAttributedString(attachmentString)
testView.attributedText = attributedTextToDisplay
self.view.addSubview(testView)
You need to allow your label to have multiple lines if you want to display more than 1 line.
var testView: UILabel = UILabel(frame: CGRectMake(20, 20, self.view.frame.width-40, self.view.frame.height-40))
testView.numberOfLines = 0;
Tips:
Use Storyboard & Interface Builder. WYSIWYG.
Use AutoLayout, it solves a lot of resizing for you
Use asserts if you are unsure of the content of a variable
Step into debugger and push on the eye icon to reveal quicklook
Eliminating all these variables will help you find out the root cause, and not worry, say, about whether or not your image is loaded. See sample below.
For good measures, I also explicitly typed a into a a:NSMutableAttributedString.
var a:NSMutableAttributedString
I had the issue when I used didSet {} on the IBOutlet, moving the code to ViewDidLoad() method solved it.

Programmatically place the Cursor inside a textField of a custom tableView header

How can you programmatically make sure that the cursor of a tableView-HeaderView-TextField gets active (i.e. is the first responder) ??
My table looks like this (i.e with the custom TextField header). So far, the cursor only gets inside the grey header field by clicking inside the textfield. But I would like to be able to get the cursor inside the textfield programmatically....
The code for my custom tableview-header looks like this :
// drawing a custom Header-View with a TextField on top of the tableView
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let container = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 50))
let textField = UITextField(frame: CGRectMake(10, 15, self.view.frame.size.width/2 - 40, 45))
textField.delegate = self
self.txtfield = textField
textField.textColor = UIColor.blackColor()
let placeholder = NSAttributedString(string: "..add player", attributes: [NSForegroundColorAttributeName: UIColor.darkGrayColor()])
textField.attributedPlaceholder = placeholder
textField.backgroundColor = UIColor.lightGrayColor()
container.addSubview(textField)
var headPlusBttn:UIButton = UIButton.buttonWithType(UIButtonType.ContactAdd) as! UIButton
headPlusBttn.center.x = self.view.frame.size.width - 20
headPlusBttn.center.y = 38
headPlusBttn.enabled = true
headPlusBttn.addTarget(self, action: "addTeam:", forControlEvents: UIControlEvents.TouchUpInside)
container.addSubview(headPlusBttn)
return container
}
My first approach was to set the first-responder of the headerViewForSection like this (see code):
// reload entries
func reloadEntries() {
self.tableView.reloadData()
// the following does unfortunately not work !!!!!
self.tableView.headerViewForSection(1)?.becomeFirstResponder()
}
Not sure why this does not work. Maybe, the Section-Nr (Int=1) is wrong. But I tried several section-numbers. No curser where it should be.
Any help appreciated !
Usually adding a delay helps in situations like this. It allows the OS to do everything it wants with the view, and then it won't mess up what you're trying to do at the same time.
Maybe something like this:
func reloadEntries() {
self.tableView.reloadData()
let delay = (Int64(NSEC_PER_SEC) * 0.1)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, triggerTime), dispatch_get_main_queue(), { () -> Void in
self.tableView.headerViewForSection(1)?.becomeFirstResponder()
})
}
I haven't tested this to do what you want, so you may need to find a different place to put this.
Also, are you sure you want to affect the view in section 1? From your image it looks like you want to mess with the header in section 0.
Be sure to drop into the debugger and check that the header isn't nil. Your code implies that that's a valid condition. You might try writing it like this (at least for testing):
if let header = self.tableView.headerViewForSection(1) {
header.becomeFirstResponder()
}
else {
print("There is no header.")
}
Try
self.tableView.headerViewForSection(1)?.textfield.becomeFirstResponder()