2014-12-09 2 views
4

Я пытаюсь нарисовать линию на UIView с помощью Swift так:Swift - UIView ничья 1 пиксель ширина линии

CGContextSetLineWidth(context, 1.0) 
CGContextBeginPath(context) 
    CGContextMoveToPoint(context, x1, y1) 
    CGContextAddLineToPoint(context, x2, y2) 
CGContextStrokePath(context) 

Но нарисованная линия является 2 пикселей шириной. Я попробовал другой способ:

CGContextSetLineWidth(context, 0.5) 
CGContextBeginPath(context) 
    CGContextMoveToPoint(context, x1, y1) 
    CGContextAddLineToPoint(context, x2, y2) 
CGContextStrokePath(context) 

Но результат был тот же. Что я делаю неправильно и как рисовать линию ширины 1 пикселя на UIView с помощью Swift?

+0

Просто любопытно; как вы могли узнать, что это было 2 пикселя? – Unheilig

+0

Возможный дубликат [CGContextSetLineWidth (контекст, 1) - ширина почти почти как минимум 2 пикселя вместо 1] (http://stackoverflow.com/questions/2033321/cgcontextsetlinewidthcontext-1-the-width-is-almost-alwayas -а-наименее-2-пиксельный) – zisoft

+0

Достойный дублирующий вопрос кандидат, но я удивлен, что нет четкого ответа, который является полным и правильным. Вероятно, есть еще один дубликат, который мне не удается найти. – rickster

ответ

8

две проблемы:

  1. CoreGraphics чертежных функции работают в терминах точек (единицы компоновки экрана, который является постоянным в приблизительном физическом размере во всех устройствах), а не в пикселях. Количество пикселей на точку отличается от устройств с разной шкалой экрана: iPad 2 и iPad mini - это единственные 1x-устройства, поддерживаемые в iOS 7 и более поздних версиях: iPhone 4, iPad 3, iPad mini 2 и более поздние версии - 2x, за исключением iPhone 6/6s/7 Plus, что составляет 3 раза. Поэтому, если вам нужна линейка волос с одним устройством, вам понадобится 0,5-точечная линия на большинстве современных устройств (и 0,3-точечная ширина на iPhone 6 Plus).

  2. Ширина линии центрирована в квадратной области одной точки. Поэтому, если у вас есть линия от (10,0, 10,0) до (10,0, 20,0), она будет находиться между пикселями с x-координатой 10,0 и 9,0 - при визуализации, сглаживание будет затенять как 10,0, так и 9,0 столбцов пикселей при 50% цвета линии, вместо затенения одного столбца на 100%. Чтобы исправить это, вам нужно поместить свою линию так, чтобы она полностью находилась в пикселе. (Пиксель устройство, а не точка макета.)

Таким образом, чтобы получить один пиксел волосяного покрова, вам нужно как уменьшить ширину линии и смещение точки вы рисуете на величину, которая изменяется в зависимости по масштабному коэффициенту экрана. Вот продолжение вашего теста, что делает это:

// pass in the scale of your UIScreen 
func drawHairline(in context: CGContext, scale: CGFloat, color: CGColor) { 

    // pick which row/column of pixels to treat as the "center" of a point 
    // through which to draw lines -- favor true center for odd scales, or 
    // offset to the side for even scales so we fall on pixel boundaries 
    let center: CGFloat 
    if Int(scale) % 2 == 0 { 
     center = 1/(scale * 2) 
    } else { 
     center = 0 
    } 

    let offset = 0.5 - center // use the "center" choice to create an offset 
    let p1 = CGPoint(x: 50 + offset, y: 50 + offset) 
    let p2 = CGPoint(x: 50 + offset, y: 75 + offset) 

    // draw line of minimal stroke width 
    let width = 1/scale 
    context.setLineWidth(width) 
    context.setStrokeColor(color) 
    context.beginPath() 
    context.move(to: p1) 
    context.addLine(to: p2) 
    context.strokePath() 
} 

Расчет centerChoice обобщает вопрос о необходимости выбрать подпункт пикселей нарисовать свою линию дальше. Вы должны провести через центр точки (смещение 0,5), чтобы затенять целый пиксель на 1-кратном дисплее или затенять только средний пиксель точки на 3-кратном дисплее, но на смещении дисплея 2x 0,5 находится между двумя пикселями которые составляют одну точку. Итак, для 2x вы должны выбрать смещение 0,25 или смещение 0,75 - линия center делает это для четных или нечетных масштабных коэффициентов в целом.

Примечание № 1: Я изменил свой тестовый пример, чтобы нарисовать вертикальную линию, потому что легче увидеть эффект от сглаживания таким образом. Диагональная линия получит некоторое сглаживание, несмотря ни на что, но вертикальная или горизонтальная линия не получит никакого сглаживания, если она имеет правильную ширину и в нужном месте.

Примечание № 2: iPhone 6/6s/7 Plus имеет логическую шкалу 3,0 и физическую шкалу дисплея около 2,61 - вы можете поиграть с screen.scale по сравнению с screen.nativeScale, чтобы увидеть, что дает вам лучшие результаты.

+0

Я сделал все как вы писали. [Здесь моя функция drawRect] (http://i.imgur.com/bdl8vO3.png), но результат тот же, [вы можете посмотреть его здесь] (http://i.imgur.com/vubcby0. PNG). Как вы можете видеть, обе линии имеют ширину в 2 пикселя. Мое устройство - iPhone 4S, поэтому ширина 0,5 должна работать, но это не так. –

+1

Мой плохой - я обобщил код для 'offset' и' width' неправильно после тестирования его с необработанными номерами. (Обобщенный код хорош только на 3х дисплеях.) На экране 2x вам нужно выбрать, какой из двух пикселей сделать точку для рисования (он не может быть центрирован, потому что это между пикселями), поэтому 'offset' имеет или 0,25 или 0,75. – rickster

+0

@Yuri: обновленный ответ, чтобы включить 2x в обобщенный расчет. Вы могли бы так же хорошо справиться с магическими числами, но мои специалисты из CS всегда говорили мне, что это плохо. :) – rickster

1

В дополнение к rickster ответ.

Вы можете использовать этот класс в storeboard:

/** View that have line with 1px on the top */ 
class lineView :UIView { 

    // pass in the scale of your UIScreen 
    func drawHairline(in context: CGContext, scale: CGFloat, color: CGColor) { 

     // pick which row/column of pixels to treat as the "center" of a point 
     // through which to draw lines -- favor true center for odd scales, or 
     // offset to the side for even scales so we fall on pixel boundaries 
     let center: CGFloat 
     if Int(scale) % 2 == 0 { 
      center = 1/(scale * 2) 
     } else { 
      center = 0 
     } 

     let offset = 0.5 - center // use the "center" choice to create an offset 
     let p1 = CGPoint(x: 0, y: 0 + offset) 
     let p2 = CGPoint(x: 800 + offset, y: 0 + offset) 

     // draw line of minimal stroke width 
     let width = 1/scale 
     context.setLineWidth(width) 
     context.setStrokeColor(color) 
     context.beginPath() 
     context.move(to: p1) 
     context.addLine(to: p2) 
     context.strokePath() 
    } 

    override func draw(_ rect: CGRect) { 
     let context = UIGraphicsGetCurrentContext() 
     drawHairline(in: context!, scale: 2, color: UIColor.gray.cgColor) 
    } 
} 
0

Я использую этот код для просмотра с 1px нижней строке.Другие примеры плохо работают на дисплеях x3, таких как 6 Plus

import UIKit 

class ViewWithBottomLine: UIView { 
    @IBInspectable var separatorColor: UIColor = Your default color 

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

     guard let context = UIGraphicsGetCurrentContext() else { 
      return 
     } 

     let scale = UIScreen.main.scale 

     let width = 1/scale 
     let offset = width/2 

     context.setLineWidth(width) 
     context.setStrokeColor(separatorColor.cgColor) 
     context.beginPath() 
     context.move(to: CGPoint(x: 0, y: rect.maxY - offset)) 
     context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - offset)) 
     context.strokePath() 
    } 
} 
Смежные вопросы