tvos: UITextView focus appearance like movies App
Asked Answered
S

2

6

I am building a tvos app and i want the UITextView to behave similarly like in tvos Movies app. I am specially interested in the focused appearence. Please have a look ate these two pictures.Normal Image

Focused TextView

Currently i am just adding background color to the textview when it is focused but how i can achieve this focused appearance in the attached images. here is my small code

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
        super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
        if context.previouslyFocusedView == lessonDescriptionTxt {

            coordinator.addCoordinatedAnimations({ () -> Void in
                self.lessonDescriptionTxt.layer.backgroundColor = UIColor.clearColor().CGColor

                }, completion: nil)
        }
        if context.nextFocusedView == lessonDescriptionTxt {

            coordinator.addCoordinatedAnimations({ () -> Void in
                self.lessonDescriptionTxt.layer.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2).CGColor

                }, completion: nil)
        }
    }

Also if someone can suggest how i can achieve this MORE feature in the textView when there is more text. I also read this question Make UILabel focusable and tappable (tvOS) but that does not do the job for me.

Specialty answered 8/12, 2015 at 14:50 Comment(0)
R
0

The "MORE" text appears only when the description can't fit in the space that's available. You should be able to measure the amount of space needed by text by using UIKit's -[NSAttributedString boundingRectWithSize:options:context:] method.

Rabkin answered 9/1, 2016 at 15:36 Comment(2)
How about the bouncy appearance when selected?Specialty
This gist gist.github.com/Cedrick84/8922a0af730c39c05794 implements a "bouncy" motion effect.Deauville
T
0

Implementing custom FocusableTextView class worked for me:

class FocusableTextView: UITextView {
let suffixWithMore = "  ...  MORE"
weak var tapDelegate: FocusableTextViewDelegate?
var currentText = ""


override init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)
    isScrollEnabled = true // must be set to true for 'stringThatFitsOnScreen' function to work
    isUserInteractionEnabled = true
    isSelectable = true
    textAlignment = .justified
    self.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
    let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
    tap.allowedPressTypes = [NSNumber(value: UIPress.PressType.select.rawValue)]
    self.addGestureRecognizer(tap)
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// use setText(_:) function instead of assigning directly to text variable
func setText(_ txt: String) {
    currentText = txt
    let stringThatFits = stringThatFitsOnScreen(originalString: txt) ?? txt
    if txt <= stringThatFits {
        self.text = txt
    } else {
      let newString = makeStringWithMORESuffix(from: stringThatFits)
      self.text = newString
    }
}

func makeStringWithMORESuffix(from txt: String) -> String {
    let newNSRange = NSMakeRange(0, txt.count - suffixWithMore.count)
    let stringRange = Range(newNSRange,in: txt)!
    let subString = String(txt[stringRange])
    return subString + suffixWithMore
}


func stringThatFitsOnScreen(originalString: String) -> String? {
    // the visible rect area the text will fit into
    let userWidth  = self.bounds.size.width - self.textContainerInset.right - self.textContainerInset.left
    let userHeight = self.bounds.size.height - self.textContainerInset.top - self.textContainerInset.bottom
    let rect = CGRect(x: 0, y: 0, width: userWidth, height: userHeight)
    
    // we need a new UITextView object to calculate the glyphRange. This is in addition to
    // the UITextView that actually shows the text (probably a IBOutlet)
    let tempTextView = UITextView(frame: self.bounds)
    tempTextView.font = self.font
    tempTextView.text = originalString
    
    // get the layout manager and use it to layout the text
    let layoutManager = tempTextView.layoutManager
    layoutManager.ensureLayout(for: tempTextView.textContainer)
    
    // get the range of text that fits in visible rect
    let rangeThatFits = layoutManager.glyphRange(forBoundingRect: rect, in: tempTextView.textContainer)
    
    // convert from NSRange to Range
    guard let stringRange = Range(rangeThatFits, in: originalString) else {
        return nil
    }
    
    // return the text that fits
    let subString = originalString[stringRange]
    return String(subString)
}


@objc func tapped(_ gesture: UITapGestureRecognizer) {
    print("user selected TextView!")
    tapDelegate?.userSelectedText(currentText)
}

override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    if (context.nextFocusedView == self) {
        backgroundColor = .white
        textColor = .black
    }
    else {
        backgroundColor = .clear
        textColor = .white
    }
}



}

protocol FocusableTextViewDelegate: AnyObject {
 func userSelectedText(_ txt: String)
}

When user taps the text View, you can present Alert with full text likewise:

extension YourViewController: FocusableTextViewDelegate{
  func userSelectedText(_ txt: String) {
    let alert = UIAlertController(title: "", message: txt, preferredStyle: .alert)
    let action = UIAlertAction( title: nil, style: .cancel) {_ in }
    alert.addAction(action)
    self.present(alert, animated: true)
  }
}

Usage:

  1. create FocusableTextView programmatically:

  2. assign constraints programmatically to textView (or use frame)

  3. set text to FocusableTextView with setText(_:) method

  4. assing your UIViewController to be the tapDelegate

     override func viewDidLoad() {
      super.viewDidLoad()
      let textView = FocusableTextView(frame: CGRect(x: 0, y: 0,
                                                    width: 500,
                                                    height: 300),
                                      textContainer: nil)
      textView.setText("Your long text..")
      textView.tapDelegate = self
     }
    
Terzas answered 8/11, 2022 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.