Skip to content

Commit

Permalink
Improved support for UIVisualEffectView
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed Jul 20, 2018
1 parent dd56240 commit 0469a6f
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 8 deletions.
41 changes: 41 additions & 0 deletions Layout/LayoutExpression.swift
Expand Up @@ -1142,6 +1142,45 @@ struct LayoutExpression {
)
}

init?(visualEffectExpression: String, for node: LayoutNode) {
let constants = RuntimeType.uiBlurEffect_Style.values
let defaultStyle = constants["regular"] as! UIBlurEffect.Style
let functions: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
.function("UIBlurEffect", arity: 0): { _ in
UIBlurEffect(style: defaultStyle)
},
.function("UIBlurEffect", arity: 1): { args in
guard let style = args[0] as? UIBlurEffect.Style else {
throw Expression.Error.message("\(Swift.type(of: args[0])) is not compatible with expected type \(UIBlurEffect.Style.self)")
}
return UIBlurEffect(style: style)
},
.function("UIVibrancyEffect", arity: 0): { _ in
UIVibrancyEffect(blurEffect: UIBlurEffect(style: defaultStyle))
},
.function("UIVibrancyEffect", arity: 1): { args in
let blurEffect: UIBlurEffect
switch args[0] {
case let style as UIBlurEffect.Style:
blurEffect = UIBlurEffect(style: style)
case let blur as UIBlurEffect:
blurEffect = blur
default:
throw Expression.Error.message("\(Swift.type(of: args[0])) is not compatible with expected type \(UIBlurEffect.self)")
}
return UIVibrancyEffect(blurEffect: blurEffect)
},
]
self.init(
anyExpression: visualEffectExpression,
type: RuntimeType(UIVisualEffect.self),
nullable: true,
constants: { constants[$0] },
pureSymbols: { functions[$0] },
for: node
)
}

init?(selectorExpression: String, for node: LayoutNode) {
guard let expression = LayoutExpression(stringExpression: selectorExpression, for: node) else {
return nil
Expand Down Expand Up @@ -1245,6 +1284,8 @@ struct LayoutExpression {
self.init(urlExpression: expression, for: node)
case is URLRequest.Type, is NSURLRequest.Type:
self.init(urlRequestExpression: expression, for: node)
case is UIVisualEffect.Type:
self.init(visualEffectExpression: expression, for: node)
default:
self.init(anyExpression: expression, type: type, nullable: false, for: node)
}
Expand Down
4 changes: 4 additions & 0 deletions Layout/LayoutNode.swift
Expand Up @@ -1102,6 +1102,10 @@ public class LayoutNode: NSObject {
// TODO: disallow setting view properties directly if type is a UIViewController
symbols.formUnion(validKeys(in: UIView.expressionTypes))
}
if type.swiftType == UIVisualEffect.self {
// TODO: any way to generalize this?
symbols.formUnion(RuntimeType.uiBlurEffect_Style.values.keys)
}
symbols.formUnion(type.values.keys)
// TODO: basing the search on type is not especially effective because
// you can use symbols of other types inside an expression, but if we
Expand Down
34 changes: 34 additions & 0 deletions Layout/RuntimeTypes.swift
Expand Up @@ -1199,4 +1199,38 @@ public extension RuntimeType {

@objc static var uiSplitViewControllerDisplayMode: RuntimeType { return uiSplitViewController_DisplayMode }
@objc static var uiSplitViewControllerPrimaryEdge: RuntimeType { return uiSplitViewController_PrimaryEdge }

// MARK: UIVisualEffectView

@objc static let uiBlurEffect_Style: RuntimeType = {
let extraDark: UIBlurEffect.Style
let regular: UIBlurEffect.Style
let prominent: UIBlurEffect.Style
if #available(iOS 10.0, *) {
#if os(tvOS)
extraDark = .extraDark
#else
extraDark = .dark
#endif
regular = .regular
prominent = .prominent
} else {
extraDark = .dark
regular = .light
prominent = .extraLight
}
return RuntimeType([
"extraLight": .extraLight,
"light": .light,
"dark": .dark,
// TODO: is there any way to warn when using these on an unsupported OS version?
"extraDark": extraDark,
"regular": regular,
"prominent": prominent,
] as [String: UIBlurEffect.Style])
}()

// Deprecated

@objc static var uiBlurEffectStyle: RuntimeType { return uiBlurEffect_Style }
}
29 changes: 28 additions & 1 deletion Layout/UIView+Layout.swift
Expand Up @@ -1376,15 +1376,42 @@ extension UIRefreshControl {
}

extension UIVisualEffectView {
open override class func create(with node: LayoutNode) throws -> UIVisualEffectView {
let defaultStyle = RuntimeType.uiBlurEffect_Style.values["regular"]! as! UIBlurEffect.Style
var effect = try node.value(forExpression: "effect") as? UIVisualEffect
let style = try node.value(forExpression: "effect.style") as? UIBlurEffect.Style
if effect == nil {
effect = UIBlurEffect(style: style ?? defaultStyle)
} else if let style = style {
switch effect {
case nil, is UIBlurEffect:
effect = UIBlurEffect(style: style)
case is UIVibrancyEffect:
effect = UIVibrancyEffect(blurEffect: UIBlurEffect(style: style))
case let effect:
throw LayoutError.message("\(type(of: effect)) does not have a style property")
}
}
return self.init(effect: effect)
}

open override class var expressionTypes: [String: RuntimeType] {
var types = super.expressionTypes
for (key, type) in UIView.cachedExpressionTypes {
types["contentView.\(key)"] = type
}
#if arch(i386) || arch(x86_64)
// Private property
// Private properties
types["backgroundEffects"] = nil
types["contentEffects"] = nil
#endif
return types
}

open override func didInsertChildNode(_ node: LayoutNode, at index: Int) {
// Insert child views into `contentView` instead of directly
contentView.didInsertChildNode(node, at: index)
}
}

private var baseURLKey = 1
Expand Down
9 changes: 2 additions & 7 deletions Layout/Utilities.swift
Expand Up @@ -350,13 +350,8 @@ struct UIntOptionSet: OptionSet {
typealias DisplayMode = UISplitViewControllerDisplayMode
}

#endif

#if swift(>=4.2)

// Workaround for https://bugs.swift.org/browse/SR-7879
extension UIEdgeInsets {
static let zero = UIEdgeInsets()
extension UIBlurEffect {
typealias Style = UIBlurEffectStyle
}

#endif
44 changes: 44 additions & 0 deletions README.md
Expand Up @@ -54,6 +54,7 @@ Layout is a native Swift framework for implementing iOS user interfaces using XM
- [UIStackView](#uistackview)
- [UITableView](#uitableview)
- [UICollectionView](#uicollectionview)
- [UIVisualEffectView](#uivisualeffectview)
- [UIWebView](#uiwebview)
- [WKWebView](#wkwebview)
- [UITabBarController](#uitabbarcontroller)
Expand Down Expand Up @@ -1507,6 +1508,7 @@ The following views and view controllers have all been tested and are known to w
* UITextView
* UIView
* UIViewController
* UIVisualEffectView
* UIWebView
* WKWebView

Expand Down Expand Up @@ -1928,6 +1930,48 @@ Layout does not currently support using XML to define supplementary `UICollectio
Layout supports the use of `UICollectionViewController`, with the same caveats as for `UITableViewController`.


## UIVisualEffectView

`UIVisualEffectView` has an `effect` property of type `UIVisualEffect`. `UIVisualEffect` is an abstract base class that is not used directly - instead you would typically set the effect to be either a `UIBlurEffect` or a `UIVibrancyEffect` (which itself contains a `UIBlurEffect`).

The `effect` property can be set programmatically, or by passing a `UIVisualEffect` instance into your `LayoutNode` as a constant or state variable:


```swift
loadLayout(
named: "MyLayout.xml",
constants: [
"blurEffect": UIBlurEffect(style: .regular),
]
)
```

```xml
<UICollectionView
effect="blurEffect"
>
```

For convenience, Layout also allows you to configure the effect directly using expressions. To configure the effect use the `UIBlurEffect(style)` or `UIVibrancyEffect(style)` constructor functions inside the `effect` expression as follows:

```xml
<UICollectionView
effect="UIVibrancyEffect(light)"
>
```

The `style` argument is of type `UIBlurEffectStyle`, and is supported for both `UIBlurEffect` and `UIVibrancyEffect`. You can set the style using a constant or state variable, or it can be set to one of the following built-in values:

* extraLight
* light
* dark
* extraDark
* regular
* prominent

**Note:** You can also use this solution for setting the `UITableView.separatorEffect` property, or any other property of type `UIVisualEffect` that is exposed in a custom view or controller.


## UIWebView

The API for `UIWebView` uses methods for loading content, which isn't directly usable from XML, so Layout exposes these methods as properties instead. To load a URL, you can use the `request` property, as follows:
Expand Down

0 comments on commit 0469a6f

Please sign in to comment.