【问题标题】:How to display only bottom border for selected item in UISegmentedControl?如何在 UISegmentedControl 中仅显示所选项目的底部边框?
【发布时间】:2017-03-13 02:09:04
【问题描述】:

我对 iOS 开发非常陌生,在为课程构建应用程序时遇到了一些麻烦。

我创建了一个分段控件,它的 init 函数(如下所示)在包含分段控件的视图控制器类中被调用。我能够从分段控件类中删除分段控件的所有边框和分隔线,如下所示:

import Foundation
import UIKit

class CashSegmentedControl: UISegmentedControl{

func initUI(){
    removeBorders()
}

func removeBorders(){
    self.tintColor = UIColor.clear

}

我希望它在选择段时在每个段下都有一条线(类似于 instagram)

我搜索了很多,并在 StackOverflow 上看到了一些帖子,但它们似乎是针对旧版本的 Swift 的。我真的很感激在这件事上的任何帮助,如果有更好的自定义边界的解决方案(除了我所做的),我很想了解更多!

非常感谢:)

【问题讨论】:

  • @TusharSharma 我已经尝试了 Gabrail 下面的答案,但它有一个小问题。你有什么其他的建议?谢谢!
  • UISegmented 自定义位复杂。您可以使用第三方代码或简单地使用 2 个按钮和 2 个蓝色背景的 UIView 并相应地显示隐藏。

标签: ios swift3 instagram uisegmentedcontrol


【解决方案1】:

在单独的 swift 文件中添加以下代码(command+N -> 新建文件):

extension UISegmentedControl{
    func removeBorder(){
        let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: self.bounds.size)
        self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
        self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
        self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)

        let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
        self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
        self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.gray], for: .normal)
        self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
    }

    func addUnderlineForSelectedSegment(){
        removeBorder()
        let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
        let underlineHeight: CGFloat = 2.0
        let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
        let underLineYPosition = self.bounds.size.height - 1.0
        let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
        let underline = UIView(frame: underlineFrame)
        underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
        underline.tag = 1
        self.addSubview(underline)
    }

    func changeUnderlinePosition(){
        guard let underline = self.viewWithTag(1) else {return}
        let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
        UIView.animate(withDuration: 0.1, animations: {
            underline.frame.origin.x = underlineFinalXPosition
        })
    }
}

extension UIImage{

    class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        let graphicsContext = UIGraphicsGetCurrentContext()
        graphicsContext?.setFillColor(color)
        let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
        graphicsContext?.fill(rectangle)
        let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return rectangleImage!
    }
}

然后从您的viewDidLoad() 方法调用segmentedControl.addUnderlineForSelectedSegment() 之后,为分段控件创建一个@IBAction 方法,如下所示:

@IBAction func segmentedControlDidChange(_ sender: UISegmentedControl){
        segmentedControl.changeUnderlinePosition()
    }

然后在这个方法中调用segmentedControl.changeUnderlinePosition()

不要忘记将情节提要中的分段控件连接到您刚刚创建的 @IBAction 方法。

非常重要:不要忘记在情节提要中使用自动布局来确定分段控件的大小和位置。

这是结果:

如果您有任何其他问题,请随时提出 :)

【讨论】:

  • 非常感谢!除了当我从第三段移动到第一段时似乎弹出的一个小故障之外,这非常有效。当我单击段 3,然后单击段 1 时,由于某种原因,第一个段有双下划线。这不会出现在任何其他过渡中。我不确定这可能在代码中发生在哪里,所以我仍在检查,但如果您有任何意见,请告诉我!再次感谢 :) 你的代码很棒!
  • @RichaNetto 您是否在情节提要中使用自动布局来确定分段控件的大小和位置?
  • 我这样做了:情节提要中的选定分段控件 -> 编辑器 -> 解决自动布局问题 -> 重置为建议的约束。你说的是这个吗?
  • 你需要显式设置分段控件的宽度:在故事板的右下角有四个自动布局工具,你想要一个叫做Add New Constraints的工具,如果你将鼠标悬停在它们上面,你没关系,图标是两行之间的正方形。单击它,然后取消选中 Constrain to margins,选择顶部和左侧的红色虚线(输入您希望它与顶部和左侧的距离),选中 @ 987654336@和height选项(输入你想要的宽度和高度),最后从'Update Frames'中选择Items of New Constraint并点击Add Constraints。 @RichaNetto
  • 上面的代码大部分都有效,但我遇到了一个奇怪的问题。仅在第一段,下划线不会是最顶层视图。我在调试视图层次结构中打开了它,我在实际的 UISegmentControl 和 UISegment CALayer 之间看到了它。但仅限于第一段。如果我单击段控件中的第二个项目,它工作正常。有人看过吗?
【解决方案2】:

A.Jam 在Xcode 9.3,Swift 4.2 版本

中的回答
import UIKit

extension UISegmentedControl {
    func removeBorder(){
        let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: self.bounds.size)
        self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
        self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
        self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)

        let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
        self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
        self.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.gray], for: .normal)
        self.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
    }

    func addUnderlineForSelectedSegment(){
        removeBorder()
        let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
        let underlineHeight: CGFloat = 2.0
        let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
        let underLineYPosition = self.bounds.size.height - 1.0
        let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
        let underline = UIView(frame: underlineFrame)
        underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
        underline.tag = 1
        self.addSubview(underline)
    }

    func changeUnderlinePosition(){
        guard let underline = self.viewWithTag(1) else {return}
        let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
        UIView.animate(withDuration: 0.1, animations: {
            underline.frame.origin.x = underlineFinalXPosition
        })
    }
}

extension UIImage {

    class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        let graphicsContext = UIGraphicsGetCurrentContext()
        graphicsContext?.setFillColor(color)
        let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
        graphicsContext?.fill(rectangle)
        let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return rectangleImage!
    }
}

【讨论】:

  • 这对我有用,但我的问题是我正在设置 segmentControl.apportionsSegmentWidthsByContent = true 现在我没有根据分段内容大小获得下划线。下划线宽度采用段的默认宽度。你知道如何克服这个问题吗??
  • 在使用黑色背景时似乎无法移除垂直分隔线。删除“setDividerImage”没有任何效果。我还有一个白色的垂直分隔线...
【解决方案3】:

你可以用这个

let segmentBottomBorder = CALayer()
segmentBottomBorder?.borderColor = UIColor.blue.cgColor
segmentBottomBorder?.borderWidth = 3
let x = CGFloat(sender.selectedSegmentIndex) * width
let y = sender.frame.size.height - (segmentBottomBorder?.borderWidth)!
let width: CGFloat = sender.frame.size.width/3
segmentBottomBorder?.frame = CGRect(x: x, y: y, width: width, height: (segmentBottomBorder?.borderWidth)!)
sender.layer.addSublayer(segmentBottomBorder!)

【讨论】:

  • 您好,感谢您的意见!这会在第一个选定项目(项目 1)段的底部获得一条边框线,但如果我尝试切换到该段中的另一个项目,则会为新选定的项目(项目 2)显示一个底部边框,并且还保留在先前选择的项目 1。所以基本上没有为先前选择的项目更新底线(它应该似乎被取消选择)。
  • 您将segmentBottomBorder设置为全局变量,每次选择segment时使用segmentBottomBorder?.removeFromSuperlayer()调用此方法
  • @Gabrail width 中的 let x = CGFloat(sender.selectedSegmentIndex) * width 是什么
【解决方案4】:

A.Jam 在 Xcode 10.1、Swift 4.2 版本中的回答 - 没有 UIImage 扩展

所有segmentIndexes都有一个灰色下划线

extension UISegmentedControl {

  func removeBorder(){

    self.tintColor = UIColor.clear
    self.backgroundColor = UIColor.clear
    self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.orange], for: .selected)
    self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.gray], for: .normal)

  }

  func setupSegment() {
    self.removeBorder()
    let segmentUnderlineWidth: CGFloat = self.bounds.width
    let segmentUnderlineHeight: CGFloat = 2.0
    let segmentUnderlineXPosition = self.bounds.minX
    let segmentUnderLineYPosition = self.bounds.size.height - 1.0
    let segmentUnderlineFrame = CGRect(x: segmentUnderlineXPosition, y: segmentUnderLineYPosition, width: segmentUnderlineWidth, height: segmentUnderlineHeight)
    let segmentUnderline = UIView(frame: segmentUnderlineFrame)
    segmentUnderline.backgroundColor = UIColor.gray

    self.addSubview(segmentUnderline)
    self.addUnderlineForSelectedSegment()

  }
  func addUnderlineForSelectedSegment(){

    let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
    let underlineHeight: CGFloat = 2.0
    let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
    let underLineYPosition = self.bounds.size.height - 1.0
    let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
    let underline = UIView(frame: underlineFrame)
    underline.backgroundColor = UIColor.orange
    underline.tag = 1
    self.addSubview(underline)
  }

  func changeUnderlinePosition(){
    guard let underline = self.viewWithTag(1) else {return}
    let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
    underline.frame.origin.x = underlineFinalXPosition

  }
}

【讨论】:

    【解决方案5】:

    在iOS13中创建WHITE分段控件

    segmentedControl.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .medium)], for: .selected)
    segmentedControl.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .medium)], for: .normal)
        if #available(iOS 13.0, *) {
            let dividerImage = UIImage(color: .white, size: CGSize(width: 1, height: 32))
            segmentedControl.setBackgroundImage(UIImage(named: "w1"), for: .normal, barMetrics: .default)
            segmentedControl.setBackgroundImage(UIImage(named: "w2"), for: .selected, barMetrics: .default)
            segmentedControl.setDividerImage(dividerImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
            segmentedControl.layer.cornerRadius = 0
            segmentedControl.layer.masksToBounds = true
        }
    
    extension UIImage {
        convenience init(color: UIColor, size: CGSize) {
            UIGraphicsBeginImageContextWithOptions(size, false, 1)
            color.set()
            let ctx = UIGraphicsGetCurrentContext()!
            ctx.fill(CGRect(origin: .zero, size: size))
            let image = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()
    
            self.init(data: image.pngData()!)!
        }
    }
    

    w1 图片是 w2 图片是

    A.Jams 回答 SWIFT 5.1 Xcode 11.3 以创建 GRAY 控件

    import Foundation
    extension UISegmentedControl {
    
        func removeBorder(){
    
            self.tintColor = UIColor.clear
            self.backgroundColor = UIColor.clear
            self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.stavkrugDarkBlue], for: .selected)
            self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.gray], for: .normal)
            if #available(iOS 13.0, *) {
                self.selectedSegmentTintColor = UIColor.clear
            } 
    
        }
    
        func setupSegment() {
            self.removeBorder()
            let segmentUnderlineWidth: CGFloat = self.bounds.width
            let segmentUnderlineHeight: CGFloat = 2.0
            let segmentUnderlineXPosition = self.bounds.minX
            let segmentUnderLineYPosition = self.bounds.size.height - 1.0
            let segmentUnderlineFrame = CGRect(x: segmentUnderlineXPosition, y: segmentUnderLineYPosition, width: segmentUnderlineWidth, height: segmentUnderlineHeight)
            let segmentUnderline = UIView(frame: segmentUnderlineFrame)
            segmentUnderline.backgroundColor = UIColor.clear
    
            self.addSubview(segmentUnderline)
            self.addUnderlineForSelectedSegment()
        }
    
        func addUnderlineForSelectedSegment(){
    
            let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
            let underlineHeight: CGFloat = 2.0
            let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
            let underLineYPosition = self.bounds.size.height - 1.0
            let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
            let underline = UIView(frame: underlineFrame)
            underline.backgroundColor = UIColor.stavkrugDarkBlue
            underline.tag = 1
            self.addSubview(underline)
    
    
        }
    
        func changeUnderlinePosition(){
            guard let underline = self.viewWithTag(1) else {return}
            let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
            underline.frame.origin.x = underlineFinalXPosition
    
        }
    }
    

    【讨论】:

      【解决方案6】:

      我自己使用公认的答案和其他人的一些花絮来实现这一点。

      变化:

      1. 它将遵循暗/亮模式外观更改,并且分段控件 TextColor 和 BGColor 将相应更改。这也将摆脱分段控制分割线。
      2. 我将setupSegment()addUnderlineForSelectedSegment() 包裹在DispatchQueue.main.asyn() 中,这样手机方向的更改将获得正确的宽度。
      3. 添加了一个名为removeUnderline() 的新函数,以在绘制新下划线之前去掉之前的下划线。这样下划线的宽度将与该方向内的实际宽度相对应。

      在哪里放东西。

      viewDidLoad() {
        yourUISegmentedControl.setupSegment()
      }
      
        /// https://stackoverflow.com/a/57943610/14414215
        /// Use this to detect changes in User Interface (Dark or Light Mode) then setup the UISegmentedControl Appearance
        override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
          yourUISegmentedControl.setupSegment()
        }
      

      注意:我确实尝试输入viewWillTransition(),但这只会检测手机方向的变化,而不是暗/亮模式外观的变化。将其放入 traitCollectionDidChange() 实际上将同时满足暗/亮模式和方向更改。

      下面的其余部分您可以直接放入它自己的 Swift 文件中。

      import UIKit
      
      extension UISegmentedControl {
        
        func removeBorder(){
          var bgcolor: CGColor
          var textColorNormal: UIColor
          var textColorSelected: UIColor
          
          if self.traitCollection.userInterfaceStyle == .dark {
            bgcolor = UIColor.black.cgColor
            textColorNormal = UIColor.gray
            textColorSelected = UIColor.white
          } else {
            bgcolor = UIColor.white.cgColor
            textColorNormal = UIColor.gray
            textColorSelected = UIColor.black
          }
          
          let backgroundImage = UIImage.getColoredRectImageWith(color: bgcolor, andSize: self.bounds.size)
          self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
          self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
          self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
          
          let deviderImage = UIImage.getColoredRectImageWith(color: bgcolor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
          self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
          self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: textColorNormal], for: .normal)
          self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: textColorSelected], for: .selected)
          
        }
        
        func setupSegment() {
          DispatchQueue.main.async() {
      
          self.removeBorder()
      // I Commented all these out as I didn't see what exactly it's used for as the color is clear anyways
      //    let segmentUnderlineWidth: CGFloat = self.bounds.width
      //    print("setupSegment UnderlineWidth:\(segmentUnderlineWidth) width:\(UIScreen.main.bounds.width) \(self.bounds.size.width)")
      //    let segmentUnderlineHeight: CGFloat = 10.0
      //    let segmentUnderlineXPosition = self.bounds.minX
      //    let segmentUnderLineYPosition = self.bounds.size.height - 4.0
      //    let segmentUnderlineFrame = CGRect(x: segmentUnderlineXPosition, y: segmentUnderLineYPosition, width: segmentUnderlineWidth, height: segmentUnderlineHeight)
      //    let segmentUnderline = UIView(frame: segmentUnderlineFrame)
      //    segmentUnderline.backgroundColor = UIColor.red
      //
      //    self.addSubview(segmentUnderline)
          self.addUnderlineForSelectedSegment()
          }
        }
        
        func addUnderlineForSelectedSegment(){
          DispatchQueue.main.async() {
            self.removeUnderline()
            let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
            let underlineHeight: CGFloat = 10.0
            let underlineXPosition = CGFloat(self.selectedSegmentIndex * Int(underlineWidth))
            let underLineYPosition = self.bounds.size.height - 4.0
            let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
            let underline = UIView(frame: underlineFrame)
            underline.backgroundColor = UIColor.darkGray
            underline.tag = 1
            self.addSubview(underline)
            
          }
        }
        
        func changeUnderlinePosition(){
          guard let underline = self.viewWithTag(1) else {return}
          let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
          underline.frame.origin.x = underlineFinalXPosition
        }
        
        func removeUnderline(){
          guard let underline = self.viewWithTag(1) else {return}
          underline.removeFromSuperview()
        }
      }
      
      
      extension UIImage{
        
        class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
          UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
          let graphicsContext = UIGraphicsGetCurrentContext()
          graphicsContext?.setFillColor(color)
          let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
          graphicsContext?.fill(rectangle)
          let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
          UIGraphicsEndImageContext()
          return rectangleImage!
        }
      }
      

      【讨论】:

      • undetLine 不改变其位置
      猜你喜欢
      • 1970-01-01
      • 2020-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-12
      • 2015-09-15
      • 1970-01-01
      相关资源
      最近更新 更多