Цвет градиентного оттенка в сегментированном элементе управления

Я получаю градиентное изображение с помощью этого метода

func gradient(size:CGSize,color:[UIColor]) -> UIImage?{
    //turn color into cgcolor
    let colors = color.map{$0.cgColor}
    //begin graphics context
    UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
    guard let context = UIGraphicsGetCurrentContext() else {
        return nil
    }
    // From now on, the context gets ended if any return happens
    defer {UIGraphicsEndImageContext()}
    //create core graphics context
    let locations:[CGFloat] = [0.0,1.0]
    guard let gredient = CGGradient.init(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as NSArray as CFArray, locations: locations) else {
        return nil
    }
    //draw the gradient
    context.drawLinearGradient(gredient, start: CGPoint(x:0.0,y:size.height), end: CGPoint(x:size.width,y:size.height), options: [])
    // Generate the image (the defer takes care of closing the context)
    return UIGraphicsGetImageFromCurrentImageContext()
}

Затем я устанавливаю tintColor сегментированного элемента управления в градиент:

    let gradientImage = gradient(size: listSegmentedControl.frame.size, color: [UIColor.black, UIColor.red])!
    listSegmentedControl.tintColor = UIColor(patternImage: gradientImage)

и это не работает. Однако тот же код работает для установки backgroundColor:

    let gradientImage = gradient(size: listSegmentedControl.frame.size, color: [UIColor.black, UIColor.red])!
    listSegmentedControl.backgroundColor = UIColor(patternImage: gradientImage)

У кого-нибудь есть идеи, почему? Мне действительно нужно установить градиент tintColor. Любая помощь очень ценится.

ИЗМЕНИТЬ:

В идеале я хочу, чтобы мой сегментированный элемент управления выглядел так:

введите здесь описание изображения


person Lavrin Pristazh    schedule 02.07.2018    source источник
comment
Это может помочь: stackoverflow.com/questions/34733943/   -  person Daniel    schedule 02.07.2018
comment
@Допапп спасибо! я попробую   -  person Lavrin Pristazh    schedule 02.07.2018


Ответы (2)


Это известный хак для изменения цвета оттенка UISegmentedControl.

   let sortedViews = listSegmentedControl.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )

    for (index, view) in sortedViews.enumerated() {
        if index == listSegmentedControl.selectedSegmentIndex {
            view.tintColor = UIColor(patternImage: gradientImage)
        } else {
            view.tintColor = UIColor.gray //Whatever the color of non selected segment controller tab
        }
    }

Хотя это выглядит как уродливый хак, я использую его довольно давно и кажется довольно простым. Надеюсь, поможет.

ИЗМЕНИТЬ:

Это то, что тебе нужно, приятель?

введите описание изображения здесь

Если да, дайте мне знать, я опубликую код для того же.

ИЗМЕНИТЬ 2:

Как OP упомянул в своем комментарии, что ожидаемый результат такой же, как тот, который я показал на изображении выше, предоставляя код для того же.

Отказ от ответственности:

Как упоминал rmaddy в своих комментариях ниже, это хак и использует недокументированный (хотя и полный общедоступный API), но очень известный хак для изменения цвета оттенка UISegemntedControl, который существует еще с iOS 5 (вот как я помню, дай мне знать, если я ошибаюсь)

Поэтому используйте ответ с осторожностью, поскольку в будущих выпусках iOS Apple может изменить структуру подпредставлений в UISegemntedControl и может повлиять на ваш O/P. Ничего из того, что я вижу, не приведет к сбою, но может повлиять на то, как O/P отображается на экране.

Я объявил переменную, чтобы GradientImage можно было сгенерировать только один раз, но ваша реализация может использовать ее так, как вы хотите.

var gradientImage : UIImage! = nil

В ViewDidLoad я инициализирую gradientImage и UISegmentedControl как

override func viewDidLoad() {
        super.viewDidLoad()
        gradientImage = gradient(size: segmentControl.frame.size, color: [UIColor.black, UIColor.red])!

        //I have specified custom font need not necessarily be used
        //Font color attribute is important though, usually `UISegementedControl` title takes color from tint color, because we might need a different color for text to highlight above gradient color am using custom font colors

        let font = UIFont(name: "HelveticaNeue-Medium", size: 20)
        segmentControl.setTitleTextAttributes([NSFontAttributeName : font!, NSForegroundColorAttributeName : UIColor.blue], for: .normal)
        segmentControl.setTitleTextAttributes([NSForegroundColorAttributeName : UIColor.white], for: .selected)

        //Set the border color and border to `UISegmentedControl` and also make it round corner

        segmentControl.layer.borderColor = UIColor(patternImage: gradientImage).cgColor
        segmentControl.layer.borderWidth = 2
        segmentControl.layer.masksToBounds = true
        segmentControl.layer.cornerRadius = 10

        //In order to update the selected Segment tint and background color we need to call multiple statements every time selection changes hence I have moved it to the function and called it in viewDidLoad

        updateGradientBackground()
    }

Наконец, определение функции updateGradientBackground такое же, как и в моем исходном ответе.

fileprivate func updateGradientBackground() {
        let sortedViews = segmentControl.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
        for (index, view) in sortedViews.enumerated() {
            if index == segmentControl.selectedSegmentIndex {
                //very important thing to notice here is because tint color was not honoring the `UIColor(patternImage` I rather used `backgroundColor` to create the effect and set clear color as clear color
                view.backgroundColor = UIColor(patternImage: self.gradientImage)
                view.tintColor = UIColor.clear
            } else {
                //very important thing to notice here is because tint color was not honoring the `UIColor(patternImage` I rather used `backgroundColor` to create the effect and set clear color as clear color
                view.backgroundColor = UIColor.white //Whatever the color of non selected segment controller tab
                view.tintColor = UIColor.clear
            }
        }
    }

Наконец, в IBAction UISegmentedControl просто вызовите

@IBAction func segmentControllerTapped(_ sender: UISegmentedControl) {
    self.updateGradientBackground()
}

Надеюсь это поможет

person Sandeep Bhandari    schedule 02.07.2018
comment
Обратите внимание, что этот код зависит от недокументированной и частной структуры подпредставления UISegmentedControl. Этот код может дать сбой при любом обновлении iOS, которое изменяет эту структуру частного подпредставления. По крайней мере, он не должен падать. - person rmaddy; 02.07.2018
comment
@rmaddy: Согласен. Поэтому я упомянул это как уродливый хак. Хак, который работает на данный момент, но работает с потенциальной опасностью взлома в будущем, как говорят, этот хак работает нормально с iOS5-6 до сих пор. Ничего не упоминая в защиту моего ответа :) - person Sandeep Bhandari; 02.07.2018
comment
Спасибо за Ваш ответ! Я реализовал ваш код в своем приложении, и он не работает. Должно быть, я делаю что-то не так. Не могли бы вы проверить это со своей стороны и сообщить мне, работает ли это для вас? - person Lavrin Pristazh; 03.07.2018
comment
@lavrin-pristazh : Сейчас проверяю - person Sandeep Bhandari; 03.07.2018
comment
@lavrin-pristazh: Но, чувак, предоставленный вами градиент не имеет такого же цветового эффекта, с предоставленным вами градиентом вы получите то, что я разместил выше. - person Sandeep Bhandari; 03.07.2018
comment
Да, извините, градиент, который я предоставил, был только для целей тестирования. Моя проблема в том, что я не могу получить эту рамку вокруг невыбранного сегмента, а углы не закруглены. - person Lavrin Pristazh; 03.07.2018
comment
@lavrin-pristazh : Взгляните на обновленное изображение, не беспокойтесь о квадратном углу, вы можете сделать его закругленным :) - person Sandeep Bhandari; 03.07.2018
comment
Да, это то, что мне нужно. Если бы вы поделились своим кодом, вы бы мне очень помогли, и я приму ваш ответ. - person Lavrin Pristazh; 03.07.2018
comment
@ lavrin-pristazh: дай мне минутку, чтобы обновить ответ - person Sandeep Bhandari; 03.07.2018
comment
@lavrin-pristazh: обновил мой ответ, внимательно прочитайте комментарии - person Sandeep Bhandari; 03.07.2018
comment
@lavrin-pristazh: Большое спасибо, приятель :) Удачного кодирования :) - person Sandeep Bhandari; 03.07.2018
comment
Я просто немного изменил ваш код и настроил свой сегментированный элемент управления именно так, как хотел. Большое спасибо, вы спасли мой день :) - person Lavrin Pristazh; 03.07.2018
comment
@lavrin-pristazh: рад, что смог помочь :) Удачного кодирования :) - person Sandeep Bhandari; 03.07.2018

Реализован градиент цвета ручки на iOS13+ UISegmentControl. Добавлено подпредставление с градиентом, затем установлено для него маска и скопирована анимация, чтобы она соответствовала оригинальной ручке.

// 1 - Make subclass
class GradientSegment: UISegmentedControl {
    // this could be UIImageView
    lazy var gradientView: GradientView = {
        let view = GradientView(frame: self.bounds)
        view.startColor = ...
        view.endColor = ...
        view.horizontalMode = true
        view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        return view
    }()

    let gradientMask = UIImageView()

    // 2 - override init
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        addSubview(gradientView)
        gradientView.mask = gradientMask
    }

    // 3 - Copy knob position and animations
    override func insertSubview(_ view: UIView, at index: Int) {
        super.insertSubview(view, at: index)
        if index == 3, let view = view as? UIImageView {
            gradientMask.image = view.image
            gradientMask.frame = view.frame

            if let keys = view.layer.animationKeys() {
                for key in keys {
                    guard let animation = view.layer.animation(forKey: key) else {
                        continue
                    }
                    gradientMask.layer.add(animation, forKey: key)
                }
            }
        }
    }
}

Это небезопасный подход, потому что внешний вид станет недействительным, когда Apple изменит расположение элементов управления, но пока он работает.

person Kirow    schedule 07.04.2021