Basit Düzen Motoru Tanıtımı
Auto Layout'u seviyorum . Karmaşık kullanıcı arabirimi tasarlarken çok yardımcı olur. Ancak, kullanıcı arayüzünün çok basit olduğu ve Otomatik Düzenin biraz abartılı olduğu zamanlar vardır, diğer zamanlarda kullanıcı arayüzünün biraz fazla karmaşık olabileceği ve Otomatik Düzenin aslında uygulama performansını etkilemeye başladığı zamanlar vardır. Otomatik mizanpajdan önce, UI oluşturmak için başka bir teknik vardı, buna Yaylar ve Destekler denir ( Otomatik Mizanpajın aksine Manuel Mizanpaj olarak da bilinir ). Manuel Düzeni sadeliği nedeniyle de çok seviyorum. Diğer tüm araçlarda olduğu gibi, iş için en iyi aracı seçerken ödünleşimler vardır ve bu aynı zamanda Otomatik Düzen ve Manuel Düzen'i seçerken de geçerlidir.
İşin iyi yanı, Otomatik Düzen, Manuel Düzen'e bir alternatif olarak tasarlanmamıştır, daha çok bir ek gibi tasarlanmıştır. Dolayısıyla a için hesaplamak yerine frame
a UIView
ile başlıyoruz CGRect.zero
ve Otomatik Düzenin frame
değeri daha sonra hesaplamasına izin veriyoruz. Çoğu zaman harikadır ve akışımızı etkilemez. frame
Diğer zamanlarda, hesaplanan değerleri geri okumak için düzen geçiş çalıştırmasını beklememiz gerekebilir .
// let Auto layout calculate the frame values
DispatchQueue.main.async {
// start using the frame values for something else.
}
Esin
İlham, veya ile nasıl UIBarButtonItem
çalıştığından gelir . Gibi bir kullanıcı arayüzü oluşturmak isteseydikUIToolbar
UINavigationBar
Bir oluşturup UIToolBar
bir demet eklerdikUIBarButtonItem
let toolbar = UIToolbar(frame: toolbarFrame)
let playButton = UIBarButtonItem(systemItem: .play)
let pauseButton = UIBarButtonItem(systemItem: .pause)
let rewindButton = UIBarButtonItem(systemItem: .rewind)
let forwardButton = UIBarButtonItem(systemItem: .fastForward)
let spaceButton = UIBarButtonItem(systemItem: .flexibleSpace)
toolbar.items = [
spaceButton,
rewindButton, spaceButton,
playButton, spaceButton,
pauseButton, spaceButton,
forwardButton, spaceButton,
]
Bence bu yaklaşım, zihinsel model açısından çok basit olan ancak istediğimiz kadar karmaşık düzenler oluşturmak için kullanılabilen bir düzen motoru oluşturmak için kullanılabilir.
Basit Düzen Motoru
Bu tasarımı göz önünde bulundurarak, düzen motorunu oluşturabiliriz. Item
a için yer tutucu olan bir sınıf ve bunlardan birini veya daha fazlasını alan ve hemen her birini hesaplayan UIView
başka bir sınıf varsa . Ardından, nesnelerimizi oluştururken hesaplanan değeri kullanabiliriz.Layout
Item
frame
Item
frame
UIView
Bu nedenle, tam ekran bir alt görünüm oluşturmak için şu şekilde oluşturabilmeliyiz:
let layout = Layout(parentFrame: frame, direction: .column)
let mainItem = try layout.add(item: .flexible)
let redView = SLECreateView(try mainItem.frame(), .red)
addSubview(redView)
private func SLECreateView(_ frame: CGRect, _ color: UIColor) -> UIView {
let view = UIView(frame: frame)
view.backgroundColor = color
return view
}
let layout = Layout(parentFrame: frame, direction: .column, alignment: .center)
try layout.add(item: .flexible)
try layout.add(item: .height(200))
let topFrame = try layout.frame(at: 0)
let bottomFrame = try layout.frame(at: 1)
addSubview(SLECreateView(topFrame, .red))
addSubview(SLECreateView(bottomFrame, .blue))
let mainLayout = Layout(parentFrame: frame, direction: .column)
try mainLayout.add(items: [.flexible, .height(44), .height(200)])
let headerFrame = try mainLayout.frame(at: 0)
let toolbarFrame = try mainLayout.frame(at: 1)
let footerFrame = try mainLayout.frame(at: 2)
addSubview(SLECreateView(headerFrame, .red))
addSubview(SLECreateView(toolbarFrame, .blue))
addSubview(SLECreateView(footerFrame, .yellow))
let contentLayout = Layout(parentFrame: footerFrame, direction: .row)
try contentLayout.add(items: [.flexible, .flexible])
let content1Frame = try contentLayout.frame(at: 0)
let content2Frame = try contentLayout.frame(at: 1)
addSubview(SLECreateView(content1Frame, .cyan))
addSubview(SLECreateView(content2Frame, .magenta))
Bu düzen motorunun uygulanmasının o kadar karmaşık olmadığı ortaya çıktı. Item
Hangi özelliklerin sabit ve diğerlerinin esnek olabileceğini sağlarsak .
public class Item {
// no values fixed
public static var flexible: Item { get }
// partially fixed
public static func width(_ value: CGFloat) -> Item
public static func height(_ value: CGFloat) -> Item
// all fixed
public static func size(_ value: CGSize) -> Item
// ...
}
public class Item {
// ...
public func frame() throws -> CGRect {
return try rect.frame()
}
internal let originalWidth: CGFloat?
internal let originalHeight: CGFloat?
private let rect = Rect()
private init(width: CGFloat?, height: CGFloat?) {
originalWidth = width
originalHeight = height
}
// called by layout engine
func updateSize(value: CGFloat,
in direction: Direction,
parentSize: CGSize) { /* update rect */ }
func updateOrigin(itemOrigin: CGPoint,
in direction: Direction,
alignment: Alignment,
parentFrame: CGRect) -> CGPoint { /* update rect */ }
}
private class Rect {
internal private(set) var width: CGFloat?
internal private(set) var height: CGFloat?
private var x: CGFloat?
private var y: CGFloat?
// read back by Item
func frame() throws -> CGRect {
guard let originX = x, let originY = y, let width = width, let height = height else {
throw LayoutError.itemIncomplete
}
return CGRect(x: originX, y: originY, width: width, height: height)
}
// set by layout engine
func set(origin: CGPoint) {
x = origin.x
y = origin.y
}
// set by layout engine
func set(size: CGSize) {
width = size.width
height = size.height
}
}
extension Layout {
public func add(item: Item) throws {
items.append(item)
try updateFrames()
}
}
private extension Layout {
func updateFrames() throws {
// calculate total flex height
var totalFlexSpace = parentFrame.height
var flexItems = 0
for item in items {
if let space = item.originalHeight {
totalFlexSpace -= space
} else {
flexItems += 1
}
}
// calculate height per flex item
let itemSpace = totalFlexSpace/CGFloat(max(flexItems, 1))
guard itemSpace >= 0 else {
throw LayoutError.outOfSpace
}
// update final frames per item
var itemOrigin = parentFrame.origin
for item in items {
item.updateSize(value: itemSpace,
in: .column,
parentSize: parentFrame.size)
itemOrigin = item.updateOrigin(itemOrigin: itemOrigin,
in: .column,
alignment: alignment,
parentFrame: parentFrame)
}
}
}
Ve şimdi, alt görünümler için hizalamayı desteklemeyi hayal etmek zor görünmüyor (şu anda hepsi ayarlandı 0.0
veya hepsi hizalandı start
) gibi bir şeyle:
public enum Alignment {
case leading
case center
case trailing
}
private extension Alignment {
func align(parent: CGFloat, item: CGFloat) -> CGFloat {
switch self {
case .leading: return 0
case .trailing: return (parent - item)
case .center: return (parent - item) / 2.0
}
}
}
let offset = alignment.align(parent: parentFrame.height, item: rect.height)
y = parentFrame.origin.y + offset
için kodun tamamı Simple Layout Engine
şu adreste mevcuttur:
Ayrıca çok daha basit bir uygulamaya sahip olduğunu düşündüğüm bir Objective-C uygulaması var.
Ve son olarak orijinal makale: