Draw a separator line in conjunction with Auto Layout

后端 未结 4 942
[愿得一人]
[愿得一人] 2021-01-03 03:35

I\'m reimplementig some kind of UISplitViewController. Now I need to draw a separator line between the master and the detail view. Now I have some questions for

相关标签:
4条回答
  • 2021-01-03 03:39

    If you need to add a true one pixel line, don't fool with an image. It's almost impossible. Just use this:

    @interface UILine : UIView
    @end
    
    @implementation UILine
    
    - (void)awakeFromNib {
    
        CGFloat sortaPixel = 1 / [UIScreen mainScreen].scale;
    
        // recall, the following...
        // CGFloat sortaPixel = 1 / self.contentScaleFactor;
        // ...does NOT work when loading from storyboard!
    
        UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, sortaPixel)];
    
        line.userInteractionEnabled = NO;
        line.backgroundColor = self.backgroundColor;
    
        line.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    
        [self addSubview:line];
    
        self.backgroundColor = [UIColor clearColor];
        self.userInteractionEnabled = NO;
    }
    
    @end
    

    How to use:

    Actually in storyboard, simply make a UIView that is in the exact place, and exact width, you want. (Feel free to use constraints/autolayout as normal.)

    Make the view say five pixels high, simply so you can see it clearly, while working.

    Make the top of the UIView exactly where you want the single-pixel line. Make the UIView the desired color of the line.

    Change the class to UILine. At run time, it will draw a perfect single-pixel line in the exact location on all devices.

    (For a vertical line class, simply modify the CGRectMake.)

    Hope it helps!

    0 讨论(0)
  • 2021-01-03 03:42

    New code

    This should work for horizontal and vertical lines:

    /// <summary>
    /// Used as separator line between UIView elements with a given color.
    /// </summary>
    public class DividerView : UIView
    {
        private UIColor color;
    
        public DividerView ()
        {
            this.color = UIColor.Black;
        }
    
        public DividerView (UIColor color)
        {
            this.color = color;
        }
    
        public override void Draw (CGRect rect)
        {
            base.Draw (rect);
    
            // get graphics context
            CGContext context = UIGraphics.GetCurrentContext ();
    
            // set up drawing attributes
            color.SetStroke ();
            color.SetFill ();
    
            // assumption: we can determine if the line is horizontal/vertical based on it's size
            nfloat lineWidth = 0;
            nfloat xStartPosition = 0;
            nfloat yStartPosition = 0;
            nfloat xEndPosition = 0;
            nfloat yEndPosition = 0;
    
            if (rect.Width > rect.Height) {
                // horizontal line
                lineWidth = rect.Height;
                xStartPosition = rect.X;
                // Move the path down by half of the line width so it doesn't straddle pixels.
                yStartPosition = rect.Y + lineWidth * 0.5f;
                xEndPosition = rect.X + rect.Width;
                yEndPosition = yStartPosition;
    
            } else {
                // vertical line
                lineWidth = rect.Width;
                // Move the path down by half of the line width so it doesn't straddle pixels.
                xStartPosition = rect.X + lineWidth * 0.5f;
                yStartPosition = rect.Y;
                xEndPosition = xStartPosition;
                yEndPosition = rect.Y + rect.Height;
    
            }
    
            // start point
            context.MoveTo (xStartPosition, yStartPosition);
    
            // end point
            context.AddLineToPoint (xEndPosition, yEndPosition);
    
            context.SetLineWidth (lineWidth);
    
            // draw the path
            context.DrawPath (CGPathDrawingMode.Stroke);
        }
    }
    

    Original answer

    Using frames in an Auto Layout project seems not suitable for me. Also I'd need the actual frames after auto layout has been applied and than I'd have to draw another view on top of it. In a fixed layout based on frames it is no problem, but not here. That's why I chose the following apporach for now:

    I created a subclass of UIView and overwrote drawRect like in Draw line in UIView or how do you draw a line programmatically from a view controller?. Here is another option.

    Because I'm using C# I give the code samples in that programming language. In the links I posted you can get the Objective-C version if you want.

    DividerView:

    using System;
    using MonoTouch.Foundation;
    using MonoTouch.UIKit;
    using MonoTouch.CoreGraphics;
    using System.CodeDom.Compiler;
    using System.Drawing;
    
    namespace ContainerProject
    {
        public class DividerView : UIView
        {
            public DividerView ()
            {
            }
    
            public override void Draw (RectangleF rect)
            {
                base.Draw (rect);
    
                // get graphics context
                CGContext context = UIGraphics.GetCurrentContext ();
    
                // set up drawing attributes
                UIColor.Black.SetStroke ();
                UIColor.Black.SetFill ();
                context.SetLineWidth (rect.Width);
    
                // start point
                context.MoveTo (rect.X, 0.0f);
                // end point
                context.AddLineToPoint (rect.X, rect.Y + rect.Height);
    
                // draw the path
                context.DrawPath (CGPathDrawingMode.Stroke);
            }
        }
    }
    

    In viewDidLoad of my container class I instantiate the DividerView with DividerView separator = new DividerView ();.

    Auto Layout:

    Then I'm using Auto Layout to layout it's position (all in viewDidLoad):

    separator.TranslatesAutoresizingMaskIntoConstraints = false;
    
    separatorTop = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Top, NSLayoutRelation.Equal, TopLayoutGuide, NSLayoutAttribute.Bottom, 1, 0);
    separatorBottom = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, BottomLayoutGuide, NSLayoutAttribute.Top, 1, 0);
    separatorRight = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Right, NSLayoutRelation.Equal, documentListController.View, NSLayoutAttribute.Left, 1, 0);
    separatorWidth = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Width, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1, 1);
    
    this.View.AddSubview (separator);
    

    Here the constraints are added:

    public override void UpdateViewConstraints ()
    {
        if (!hasLoadedConstraints) {
            this.View.AddConstraints (new NSLayoutConstraint[] {
                separatorTop,
                separatorBottom,
                separatorRight,
                separatorWidth
            });
    
            hasLoadedConstraints = true;
        }
        base.UpdateViewConstraints ();
    }
    

    Result:

    This approach seems to work. Now my separator line is overlapping my detail view (only the first point). One could adapt the values or changes the constraints of the master and the detail view so that there is room between those for the separator.

    Alternatives:

    One could also add a subview to the content view of each UITableViewCell. Examples can be found here or here.

    0 讨论(0)
  • 2021-01-03 03:43

    Updating the @mharper answer to Swift 3.x

    import UIKit
    
    @IBDesignable
    class LineView: UIView {
    
        @IBInspectable var lineWidth: CGFloat = 1.0
        @IBInspectable var lineColor: UIColor? {
            didSet {
                lineCGColor = lineColor?.cgColor
            }
        }
        var lineCGColor: CGColor?
    
        override func draw(_ rect: CGRect) {
            // Draw a line from the left to the right at the midpoint of the view's rect height.
            let midpoint = self.bounds.size.height / 2.0
            if let context = UIGraphicsGetCurrentContext() {
                context.setLineWidth(lineWidth)
                if let lineCGColor = self.lineCGColor {
                    context.setStrokeColor(lineCGColor)
                }
                else {
                    context.setStrokeColor(UIColor.black.cgColor)
                }
                context.move(to: CGPoint(x: 0.0, y: midpoint))
                context.addLine(to: CGPoint(x: self.bounds.size.width, y: midpoint))
                context.strokePath()
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-03 03:49

    I took @joe-blow's excellent answer a step further and created a view that is not only rendered in IB but also whose line width and line color can be changed via the inspector in IB. Simply add a UIView to your storyboard, size appropriately, and change the class to LineView.

    import UIKit
    
    @IBDesignable
    class LineView: UIView {
    
      @IBInspectable var lineWidth: CGFloat = 1.0
      @IBInspectable var lineColor: UIColor? {
        didSet {
          lineCGColor = lineColor?.CGColor
        }
      }
      var lineCGColor: CGColorRef?
    
        override func drawRect(rect: CGRect) {
          // Draw a line from the left to the right at the midpoint of the view's rect height.
          let midpoint = self.bounds.size.height / 2.0
          let context = UIGraphicsGetCurrentContext()
          CGContextSetLineWidth(context, lineWidth)
          if let lineCGColor = self.lineCGColor {
            CGContextSetStrokeColorWithColor(context, lineCGColor)
          }
          else {
            CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
          }
          CGContextMoveToPoint(context, 0.0, midpoint)
          CGContextAddLineToPoint(context, self.bounds.size.width, midpoint)
          CGContextStrokePath(context)
        }
    }
    

    0 讨论(0)
提交回复
热议问题