在Swift

时间:2017-01-03 09:09:56

标签: ios swift xcode storyboard background-thread

我有一个具有复杂UI视图的子控制器。我的视图是在故事板中设计的。这会导致屏幕冻结,如果用户继续点击冻结屏幕,则会增加崩溃的可能性。

是否可以在不同的线程中加载UI并向用户显示活动指示符?

我知道complixity位于中间的复选框中。复选框是一个自定义的uibuttons。我在drawRect上绘制它们。取决于选择,边框宽度,动态边框颜色,动态选定边框颜色,背景颜色,选择背景颜色。

enter image description here

修改 请注意,superview标记不是500.这是一个多选视图。

func setupCheckBox(checkbox: CheckBox) {
    checkbox.setCornerRadius(radius: CGSize(width: checkbox.frame.size.width * 0.5, height: checkbox.frame.size.height * 0.5))
    checkbox.setBorderWidth(width: 2.0)
    checkbox.setBorderColor(border_color: UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0))
    checkbox.setSelection(selection_color: UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 0.9))
    checkbox.setSelected(selection: false)
    checkbox.setHit(edgeInsets: UIEdgeInsets(top: -4, left: -4, bottom: -4, right: -4))
    checkbox.delegate = self
}

CheckBox实施:

protocol CheckBoxProtocol: class {
    func checkbox(checkBox: UIView, selection: Bool)
}

class CheckBox: RoundedCornersButton {

    var checked_icon: String?
    var unchecked_icon: String?

    weak var delegate: CheckBoxProtocol?

    var selectedd: Bool = false
    var allow_none: Bool = false
    var hitEdgeInsets: UIEdgeInsets?

    var selection_color: UIColor?

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        setHit(edgeInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))

        selection_color = .clear
        backgroundColor = .clear
        addTarget(self, action: #selector(didTouchButton), for: .touchUpInside)
    }

    func setCheckedIcon(checked_icon: String) {
        self.checked_icon = checked_icon
        setSelectionTo(button: self, selected: selectedd, inform: nil)
    }

    func setUncheckedIcon(unchecked_icon: String) {
        self.unchecked_icon = unchecked_icon
        setSelectionTo(button: self, selected: selectedd, inform: nil)
    }

    func setSelection(selection_color: UIColor) {
        self.selection_color = selection_color
        setNeedsDisplay()
        layoutIfNeeded()
    }

    // if superview tag is equal to 500. all other checkbox in the superview will deselected.
    func didTouchButton() {
        if superview?.tag == 500 {
            for button: UIView in (superview?.subviews)! {
                if button is CheckBox && button != self {
                    setSelectionTo(button: button as! CheckBox, selected: false, inform:delegate)
                }
            }
            setSelectionTo(button: self as CheckBox, selected: allow_none ? (!selectedd) : true, inform:delegate)
        } else {
            setSelectionTo(button: self as CheckBox, selected: !selectedd, inform:delegate)
        }
    }

    func setSelectionTo(button: CheckBox, selected: Bool, inform: CheckBoxProtocol?) {
        button.selectedd = selected
        if selected {
            if checked_icon != nil {
                (button as CheckBox).setImage(UIImage.init(named: checked_icon!), for: .normal)
            }

            if color != .clear {
                button.setTitleColor(color, for: .normal)
            }
        } else {
            if unchecked_icon != nil {
                (button as CheckBox).setImage(UIImage.init(named: unchecked_icon!), for: .normal)
            }

            if selection_color != .clear {
                button.setTitleColor(selection_color, for: .normal)
            }
        }

        button.setNeedsDisplay()
        button.layoutIfNeeded()
        inform?.checkbox(checkBox: button, selection: selected)
    }

    func setSelected(selection: Bool) {
        super.isSelected = selection
        setSelectionTo(button: self, selected: selection, inform: nil)
        setNeedsDisplay()
        layoutIfNeeded()
    }

    func setHit(edgeInsets: UIEdgeInsets)
    {
        hitEdgeInsets = edgeInsets
    }

    // handeling hits on checkbox. taking in count the hitEdgeInsets. if hitEdgeInsets have minus values hits around the checkbox will be considered as a valid hits.
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(hitEdgeInsets!, .zero) || !isEnabled || isHidden {
            return super.point(inside: point, with: event)
        }

        let relativeFrame: CGRect = self.bounds
        let hitFrame: CGRect = UIEdgeInsetsInsetRect(relativeFrame, hitEdgeInsets!)

        return hitFrame.contains(point)
    }

    func isSelectedd() -> Bool {
        return selectedd
    }

    func setAllowNone(_ allow: Bool) {
        allow_none = allow
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        let context: CGContext = UIGraphicsGetCurrentContext()!

        context.setFillColor((selection_color?.cgColor)!)
        context.setStrokeColor((selection_color?.cgColor)!)

        let circlePath: UIBezierPath = UIBezierPath.init(arcCenter: CGPoint(x: frame.size.width * 0.5, y: frame.size.width * 0.5), radius: frame.size.width * 0.5 - borderWidth, startAngle: 0, endAngle: CGFloat(2.0 * Float(M_PI)), clockwise: true)

        if isSelectedd() {
            circlePath.fill()
            circlePath.stroke()
        }
    }
}

圆角落实施

class RoundedCornersButton: UIButton {

    var cornorRadius: CGSize!
    var borderWidth: CGFloat!
    var borderColor: UIColor!

    var color: UIColor!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        cornorRadius = CGSize(width: 12, height: 12)
        borderWidth = 0
        borderColor = UIColor.clear

        self.titleLabel?.numberOfLines = 1
        self.titleLabel?.adjustsFontSizeToFitWidth = true
        self.titleLabel?.lineBreakMode = .byClipping
        self.titleLabel?.baselineAdjustment = .alignCenters
        self.titleLabel?.textAlignment = .center

        // clear the background color to draw it on the graphics layer
        color = self.backgroundColor
        self.backgroundColor = UIColor.clear
    }

    func setColor(_color: UIColor) {
        color = _color
        self.setNeedsDisplay()
        self.layoutIfNeeded()
    }

    func setCornerRadius(radius: CGSize) {
        cornorRadius = radius
        self.setNeedsDisplay()
        self.layoutIfNeeded()
    }

    func setBorderWidth(width: CGFloat) {
        borderWidth = width
        self.setNeedsDisplay()
        self.layoutIfNeeded()
    }

    func setBorderColor(border_color: UIColor) {
        borderColor = border_color
        self.setNeedsDisplay()
        self.layoutIfNeeded()
    }

    // redraw without increasing or decreasing hiting area.
    override func draw(_ rect: CGRect) {
        // Drawing code
        super.draw(rect)

        // drawing context
        let context: CGContext = UIGraphicsGetCurrentContext()!

        // set fill color
        context.setFillColor(color.cgColor)

        // theme color
        let themeColor: UIColor = borderColor

        // set stroke color
        context.setStrokeColor(themeColor.cgColor.components!)

        // corners to round
        let corners: UIRectCorner = UIRectCorner.allCorners

        // bezier path rect

        let bezierRect: CGRect = CGRect(x: rect.origin.x+borderWidth*0.5, y: rect.origin.y+borderWidth*0.5, width: rect.size.width-borderWidth, height: rect.size.height-borderWidth)

        // rounded path
        let roundedPath: UIBezierPath = UIBezierPath.init(roundedRect: bezierRect, byRoundingCorners: corners, cornerRadii: cornorRadius)

        // set stroke width
        roundedPath.lineWidth = borderWidth

        // fill coloring
        roundedPath.fill()

        // stroke coloring
        roundedPath.stroke()
    }
}

3 个答案:

答案 0 :(得分:2)

每当您需要触摸涉及UI的任何内容时,请将该代码放入主队列的调度中。对于Swift 3,请参阅新的DispatchQueue机制,否则,请使用好的'dispatch_async(...)并提供主队列。一个很好的教程是https://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1

答案 1 :(得分:1)

Apple文档说,

  

“如果您的应用程序具有图形用户界面,建议您使用   您收到与用户相关的事件并启动界面更新   从您的应用程序的主线程。这种方法有助于避免   与处理用户事件相关的同步问题   绘图窗口内容。一些框架,例如Cocoa   需要这种行为,但即使对于那些不这样做,也要保持这种行为   主线程上的行为具有简化逻辑的优点   用于管理用户界面。“

有关详细信息,请Click here

答案 2 :(得分:1)

  

我有一个具有复杂UI视图的子控制器。

你在这里展示的并不复杂。我猜你的意思是计算一切都很复杂。如果这是真的,您需要计算除绘制周期之外的一些时间。

  

这导致屏幕冻结,如果用户继续点击冻结的屏幕,则会增加崩溃的可能性。

如果由于用户与冻结视图交互而导致崩溃,那么您的问题就不是您认为的那样。如果您挂起事件循环,则将忽略所有用户触摸(因为它们也在事件循环中)。但是如果你花了很长时间从事件循环中返回,你可能会崩溃(我记得~~ 2s,尽管这些日子可能会更短)。往上看;你必须在其他地方移动任何复杂的计算。

  

我知道complixity位于中间的复选框中。复选框是一个自定义的uibuttons。我在drawRect上绘制它们。取决于选择,边框宽度,动态边框颜色,动态选定边框颜色,背景颜色,选择背景颜色。

优异。追逐问题发生的地方是大多数解决方案。不过,你的drawRect对于这些应该是非常微不足道的。他们只是圈子。如果您的drawRect正在执行除检查预先计算的值之外的任何操作,那么您需要将所有计算移动到其他位置。 drawRect通常不应该计算得太多。它必须非常快。

如果您的问题是需要计算很多并且计算本身需要很长时间,那么您需要将其移动到后台队列,在计算它时显示占位符视图,以及何时完成绘制真实的观点。

如果您的问题是我们看到的子视图数量多得多,您可能需要减少子视图的数量并进行更多自定义绘制(但您提供的屏幕截图看起来并不复杂)。

但是在一天结束时,必须更新主队列上的UI,并且必须阻止主队列。因此,如果您有任何复杂的事情要做,您需要在另一个队列上执行此操作并在完成后更新UI。

如果您的问题是从Storyboard中取出内容所需的实际时间,您可以尝试将此片段提取到自己的Storyboard或NIB中,或者以编程方式绘制它(或者只是减少Storyboard中的元素数量),但是我从来没有亲自遇到过这样的问题。