Inverse UIlabel text color when its frame intersects an UIView
Asked Answered
B

4

13

Hi Im trying to change the text color of a UILabel in the case when the labels rect intersects an UIView, i was thinking maybe there is a property or something i can use. Like in the image below:

enter image description here

Did someone meet this issue before, or what should i read to start doing this thing ?

Thanks in advance

Batman answered 21/3, 2013 at 1:59 Comment(0)
H
12

Good puzzle! Here's what I would do:

  1. Two UIViews. Let's call one the background and the other the progressBar. progressBar is stacked on top of background with the same origin on their common superview.
  2. They both have a UILabel as subview, and both labels at the same origin relative to their parent. background has a dark backgroundColor and it's label has light textColor and the progress view has things the other way around.
  3. progressBar has a narrower frame width than background and has clipsToBounds==YES

The trick is, with the views' origins the same and the labels' origins the same, and clipsToBounds on the top view, everything is going to look right.

Drop those two views into a new UIView subclass called ReallyCoolProgressView, and give it one public method:

-(void)setProgress:(float)progress progress is a number from 0.0 to 1.0. The method scales the progressBar width and sets both label's text @"Progress %f", progress*100

Hootchykootchy answered 21/3, 2013 at 3:43 Comment(2)
This seemed fun, so I built it and pasted here: pastebin.com/ZCDmdpPq It worked in a few quick tests.Hootchykootchy
hum, yeah that would be a nice trick, the fastest and the easiest one. Thank you !!Batman
A
3

Built an example in Swift for anyone that's looking for a practical application. Great guidance on this danh - thanks!


import UIKit

class CDSlideView: UIView {

    var leftBackView: UIView!
    var leftBackLabel: UILabel!
    var leftFrontView: UIView!
    var leftFrontLabel: UILabel!

    var rightBackView: UIView!
    var rightBackLabel: UILabel!
    var rightFrontView: UIView!
    var rightFrontLabel: UILabel!

    var foregroundView: UIView!
    var backgroundView: UIView!

    var slideGesture: UIPanGestureRecognizer!

    let lightColor: UIColor = UIColor.whiteColor()
    let darkColor: UIColor = UIColor.blueColor()
    let leftText: String = "Search"
    let rightText: String = "New"

    var viewWidth: CGFloat!
    var viewHeight: CGFloat!
    var leftOrigin: CGFloat!
    var rightOrigin: CGFloat!
    var foregroundPadding: CGFloat = 4

    override init(frame: CGRect) {
        super.init(frame: frame)

        // Init variables variables
        viewWidth = self.frame.size.width
        viewHeight = self.frame.size.height
        leftOrigin = foregroundPadding / 2
        rightOrigin = (viewWidth - foregroundPadding) / 2 + foregroundPadding / 2

        backgroundView = UIView()
        backgroundView.frame = CGRectMake(0, 0, viewWidth, viewHeight)
        backgroundView.layer.cornerRadius = backgroundView.frame.size.height / 2
        self.addSubview(backgroundView)

        leftBackView = UIView()
        leftBackView.frame = CGRectMake(0, 0, backgroundView.frame.size.width / 2, backgroundView.frame.size.height)
        self.backgroundView.addSubview(leftBackView)

        leftBackLabel = UILabel()
        leftBackLabel.frame = CGRectMake(0, 0, leftBackView.frame.size.width, leftBackView.frame.size.height)
        leftBackLabel.font = UIFont.systemFontOfSize(13, weight: UIFontWeightSemibold)
        leftBackLabel.backgroundColor = UIColor.clearColor()
        leftBackLabel.lineBreakMode = .ByClipping
        leftBackLabel.textAlignment = .Center
        self.leftBackView.addSubview(leftBackLabel)

        rightBackView = UIView()
        rightBackView.frame = CGRectMake(backgroundView.frame.size.width / 2, 0, backgroundView.frame.size.width / 2, backgroundView.frame.size.height)
        self.backgroundView.addSubview(rightBackView)

        rightBackLabel = UILabel()
        rightBackLabel.frame = CGRectMake(0, 0, rightBackView.frame.size.width, rightBackView.frame.size.height)
        rightBackLabel.font = UIFont.systemFontOfSize(13, weight: UIFontWeightSemibold)
        rightBackLabel.backgroundColor = UIColor.clearColor()
        rightBackLabel.lineBreakMode = .ByClipping
        rightBackLabel.textAlignment = .Center
        self.rightBackView.addSubview(rightBackLabel)

        foregroundView = UIView()
        foregroundView.frame = CGRectMake(foregroundPadding / 2, foregroundPadding / 2, (backgroundView.frame.size.width - foregroundPadding) / 2, backgroundView.frame.size.height - foregroundPadding)
        foregroundView.clipsToBounds = true
        foregroundView.layer.cornerRadius = (foregroundView.frame.size.height - foregroundPadding / 2) / 2
        self.addSubview(foregroundView)

        slideGesture = UIPanGestureRecognizer(target: self, action: #selector(CDSlideView.slideAction))
        self.foregroundView.addGestureRecognizer(slideGesture)

        leftFrontView = UIView()
        leftFrontView.frame = CGRectMake(0, 0, backgroundView.frame.size.width / 2, backgroundView.frame.size.height)
        self.foregroundView.addSubview(leftFrontView)

        leftFrontLabel = UILabel()
        leftFrontLabel.font = UIFont.systemFontOfSize(13, weight: UIFontWeightSemibold)
        leftFrontLabel.backgroundColor = UIColor.clearColor()
        leftFrontLabel.translatesAutoresizingMaskIntoConstraints = false
        leftFrontLabel.lineBreakMode = .ByClipping
        leftFrontLabel.textAlignment = .Center
        self.leftFrontView.addSubview(leftFrontLabel)

        let leftFrontLabelLeadingConstraint = NSLayoutConstraint(item: leftFrontLabel, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1, constant: self.backgroundView.frame.origin.x)
        self.addConstraint(leftFrontLabelLeadingConstraint)

        let leftFrontLabelTopConstraint = NSLayoutConstraint(item: leftFrontLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: self.backgroundView.frame.origin.y)
        self.addConstraint(leftFrontLabelTopConstraint)

        let leftFrontLabelWidthConstraint = NSLayoutConstraint(item: leftFrontLabel, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: leftFrontView.frame.size.width)
        self.addConstraint(leftFrontLabelWidthConstraint)

        let leftFrontLabelHeightConstraint = NSLayoutConstraint(item: leftFrontLabel, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: leftFrontView.frame.size.height)
        self.addConstraint(leftFrontLabelHeightConstraint)

        rightFrontView = UIView()
        rightFrontView.frame = CGRectMake(backgroundView.frame.size.width / 2, 0, backgroundView.frame.size.width / 2, backgroundView.frame.size.height)
        self.foregroundView.addSubview(rightFrontView)

        rightFrontLabel = UILabel()
        rightFrontLabel.font = UIFont.systemFontOfSize(13, weight: UIFontWeightSemibold)
        rightFrontLabel.backgroundColor = UIColor.clearColor()
        rightFrontLabel.translatesAutoresizingMaskIntoConstraints = false
        rightFrontLabel.lineBreakMode = .ByClipping
        rightFrontLabel.textAlignment = .Center
        self.rightFrontView.addSubview(rightFrontLabel)

        let rightFrontLabelTrailingConstraint = NSLayoutConstraint(item: self, attribute: .Trailing, relatedBy: .Equal, toItem: rightFrontLabel, attribute: .Trailing, multiplier: 1, constant: self.backgroundView.frame.origin.x)
        self.addConstraint(rightFrontLabelTrailingConstraint)

        let rightFrontLabelTopConstraint = NSLayoutConstraint(item: rightFrontLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: self.backgroundView.frame.origin.y)
        self.addConstraint(rightFrontLabelTopConstraint)

        let rightFrontLabelWidthConstraint = NSLayoutConstraint(item: rightFrontLabel, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: rightFrontView.frame.size.width)
        self.addConstraint(rightFrontLabelWidthConstraint)

        let rightFrontLabelHeightConstraint = NSLayoutConstraint(item: rightFrontLabel, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: rightFrontView.frame.size.height)
        self.addConstraint(rightFrontLabelHeightConstraint)

        let leftTapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(CDSlideView.leftTap(_:)))
        self.leftBackView.addGestureRecognizer(leftTapGesture)

        let rightTapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(CDSlideView.rightTap(_:)))
        self.rightBackView.addGestureRecognizer(rightTapGesture)

        self.setLabelText(leftText, rightText: rightText)
        self.setLightColor(lightColor)
        self.setDarkColor(darkColor)
    }

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

    // MARK: Setup
    func setLightColor(lightColor: UIColor) {
        let lightColor = lightColor
        self.foregroundView.backgroundColor = lightColor
        self.leftBackLabel.textColor = lightColor
        self.rightBackLabel.textColor = lightColor
    }

    func setDarkColor(darkColor: UIColor) {
        let darkColor = darkColor
        self.backgroundView.backgroundColor = darkColor
        self.leftFrontLabel.textColor = darkColor
        self.rightFrontLabel.textColor = darkColor
    }

    func setLabelText(leftText: String, rightText: String) {
        self.leftFrontLabel.text = leftText
        self.leftBackLabel.text = leftText
        self.rightFrontLabel.text = rightText
        self.rightBackLabel.text = rightText
    }

    // MARK: Actions
    func slideAction(sender: UIPanGestureRecognizer) {
        if sender.state == .Began || sender.state == .Changed {
            let translation = sender.translationInView(self)

            // Calculate where the user is trying to drag
            var newCenter: CGPoint = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y)

            // Limit bounds & update center
            newCenter.x = max(self.frame.size.width * 0.25 + foregroundPadding / 2, newCenter.x)
            newCenter.x = min(self.frame.size.width * 0.75 - foregroundPadding / 2, newCenter.x)

            // Set new center
            sender.view!.center = newCenter
            sender.setTranslation(CGPointMake(0,0), inView: self)
        } else if sender.state == .Ended {
            let senderVCX = sender.view?.center.x

            // Snap to side
            if senderVCX <= viewWidth / 2 {
                print("called left")
                sender.view?.frame.origin.x = self.leftOrigin
            } else {
                print("called right")
                sender.view?.frame.origin.x = self.rightOrigin
            }
        }
    }

    func leftTap(sender: UITapGestureRecognizer) {
        UIView.animateWithDuration(0.05) {
            self.foregroundView.frame.origin.x = self.leftOrigin
        }
    }

    func rightTap(sender: UITapGestureRecognizer) {
        UIView.animateWithDuration(0.05) {
            self.foregroundView.frame.origin.x = self.rightOrigin
        }
    }
}
Accord answered 6/6, 2016 at 14:43 Comment(0)
W
1

I would suggest you to have a look at these methods:

You may check how much of your string overlaps with the view by checking NSString's size, with this method:

[yourString sizeWithFont:yourFont constrainedToSize:maximumAllowedSize];

Then you can use a NSMutableAttributedString for setting part of the label's strings to one color and the latter part with another color.

//Just an example usage 
//It sets the color of characters starting from 3 to the end to blue
[str addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(3, str.length)];


//Now you have to set your string as attributed text just like below
//UILabels now support attributed strings by default which is quite handy

[yourLabel setAttributedString:str];
Wrac answered 21/3, 2013 at 2:15 Comment(2)
This will not work for letters that are only partially covered though. I think @Batman will need to do something clever with CoreGraphics and a custom drawRect to get the effect he's looking for.Postgraduate
yeah you are right, he will need to draw his own strings to support also partial ones.Wrac
N
1

Take a look at this tutorial for creating alpha masks with text. Using this technique you can implement a standard progress bar that moves across both the foreground and the background view to achieve the effect you want.

Nonbeliever answered 21/3, 2013 at 3:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.