Skip to content

Commit

Permalink
Support alpha threshold
Browse files Browse the repository at this point in the history
  • Loading branch information
ya.wang committed Dec 3, 2023
1 parent b561a2d commit 619067f
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 35 deletions.
18 changes: 16 additions & 2 deletions ImpressionKit/ImpressionGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ public class ImpressionGroup<IndexType: Hashable> {
// Chage the threshold of duration in screen (in seconds). The view will be impressed if it keeps being in screen after this seconds. Apply to the group. `UIView.durationThreshold` will be used if it's nil.
public var durationThreshold: Float?

// Chage the threshold of area ratio in screen. It's from 0 to 1. The view will be impressed if it's area ratio keeps being bigger than this value. Apply to the group. `UIView.areaRatioThreshold` will be used if it's nil.
// Chage the threshold of area ratio in screen. It's from 0 to 1. The view will be impressed if it's area ratio remains equal to or greater than this value. Apply to the group. `UIView.areaRatioThreshold` will be used if it's nil.
public var areaRatioThreshold: Float?

// Chage the threshold of alpha. It's from 0 to 1. The view will be impressed if it's alpha is equal to or greater than this value. Apply to the group. `UIView.alphaThreshold` will be used if it's nil.
public var alphaThreshold: Float?

// Retrigger the impression. Apply to the group. `UIView.redetectOptions` will be used if it's nil.
public var redetectOptions: UIView.Redetect? {
Expand Down Expand Up @@ -69,7 +72,17 @@ public class ImpressionGroup<IndexType: Hashable> {
self.changeState(index: index, view: view, state: state)
}

public init(impressionGroupCallback: @escaping ImpressionGroupCallback) {
public init(detectionInterval: Float? = nil,
durationThreshold: Float? = nil,
areaRatioThreshold: Float? = nil,
alphaThreshold: Float? = nil,
redetectOptions: UIView.Redetect? = nil,
impressionGroupCallback: @escaping ImpressionGroupCallback) {
self.detectionInterval = detectionInterval
self.durationThreshold = durationThreshold
self.areaRatioThreshold = areaRatioThreshold
self.alphaThreshold = alphaThreshold
self.redetectOptions = redetectOptions
self.impressionGroupCallback = impressionGroupCallback
readdNotificationObserver()
}
Expand Down Expand Up @@ -103,6 +116,7 @@ public class ImpressionGroup<IndexType: Hashable> {
view.detectionInterval = self.detectionInterval
view.durationThreshold = self.durationThreshold
view.areaRatioThreshold = self.areaRatioThreshold
view.alphaThreshold = self.alphaThreshold
// No need to set .willResignActive and .didEnterBackground for views. Group will handle this.
var redetectOptions = self.redetectOptions
redetectOptions?.remove(.willResignActive)
Expand Down
9 changes: 5 additions & 4 deletions ImpressionKit/UIViewFunctionExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ extension UIView {
// MARK: - Algorithm

private func areaRatio() -> Float {
guard self.isHidden == false && self.alpha > 0 else {
let alphaThreshold = CGFloat(self.alphaThreshold ?? UIView.alphaThreshold)
guard self.isHidden == false && self.alpha >= alphaThreshold else {
return 0
}
if let window = self as? UIWindow,
Expand All @@ -219,14 +220,14 @@ extension UIView {
} else {
// It's normal view
guard let window = self.window,
window.isHidden == false && window.alpha > 0 else {
window.isHidden == false && window.alpha >= alphaThreshold else {
return 0
}
// If super view hidden or alpha <= 0, self can't show
// If super view hidden or alpha < alphaThreshold, self can't show
var aView = self
var frameInSuperView = self.bounds
while let superView = aView.superview {
guard superView.isHidden == false && superView.alpha > 0 else {
guard superView.isHidden == false && superView.alpha >= alphaThreshold else {
return 0
}
frameInSuperView = aView.convert(frameInSuperView, to: superView)
Expand Down
43 changes: 32 additions & 11 deletions ImpressionKit/UIViewPropertyExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private var stateKey = 0
private var detectionIntervalKey = 0
private var durationThresholdKey = 0
private var areaRatioThresholdKey = 0
private var alphaThresholdKey = 0
private var redetectOptionsKey = 0
private var hookingDeallocTokenKey = 0
private var hookingDidMoveToWindowTokenKey = 0
Expand Down Expand Up @@ -95,10 +96,10 @@ extension UIView {
}
}

// Chage the threshold of area ratio in screen. It's from 0 to 1. The view will be impressed if it's area ratio keeps being bigger than this value. Apply to all views
// Chage the threshold of area ratio in screen. It's from 0 to 1. The view will be impressed if it's area ratio remains equal to or greater than this value. Apply to all views
public static var areaRatioThreshold: Float = 0.5

// Chage the threshold of area ratio in screen. It's from 0 to 1. The view will be impressed if it's area ratio keeps being bigger than this value. Apply to the specific view. `UIView.areaRatioThreshold` will be used if it's nil.
// Chage the threshold of area ratio in screen. It's from 0 to 1. The view will be impressed if it's area ratio remains equal to or greater than this value. Apply to the specific view. `UIView.areaRatioThreshold` will be used if it's nil.
public var areaRatioThreshold: Float? {
set {
objc_setAssociatedObject(self, &areaRatioThresholdKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
Expand All @@ -109,17 +110,31 @@ extension UIView {
}
}

// Chage the threshold of alpha. It's from 0 to 1. The view will be impressed if it's alpha is equal to or greater than this value. Apply to all views
public static var alphaThreshold: Float = 0.1

// Chage the threshold of alpha. It's from 0 to 1. The view will be impressed if it's alpha is equal to or greater than this value. Apply to the specific view. `UIView.alphaThreshold` will be used if it's nil.
public var alphaThreshold: Float? {
set {
objc_setAssociatedObject(self, &alphaThresholdKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}

get {
return objc_getAssociatedObject(self, &alphaThresholdKey) as? Float
}
}

public struct Redetect: OptionSet {

public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}

// Retrigger the impression event when a view has left from the screen (The UIViewController (page) is still here, Just the view is out of the screen).
// Retrigger the impression event when a view left from the screen (The UIViewController (page) is still here, Just the view is out of the screen).
public static let leftScreen = Redetect(rawValue: 1 << 0)

// Retrigger the impression event when the UIViewController which the view in did disappear.
// Retrigger the impression event when the UIViewController of the view disappear.
public static let viewControllerDidDisappear = Redetect(rawValue: 1 << 1)

// Retrigger the impression event when the App did enter background.
Expand Down Expand Up @@ -147,37 +162,43 @@ extension UIView {

var hookingDeallocToken: Token? {
set {
objc_setAssociatedObject(self, &hookingDeallocTokenKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let closure = { return newValue }
objc_setAssociatedObject(self, &hookingDeallocTokenKey, closure, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}

get {
return objc_getAssociatedObject(self, &hookingDeallocTokenKey) as? Token
guard let closure = objc_getAssociatedObject(self, &hookingDeallocTokenKey) as? () -> Token? else { return nil }
return closure()
}
}

var hookingDidMoveToWindowToken: Token? {
set {
objc_setAssociatedObject(self, &hookingDidMoveToWindowTokenKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let closure = { return newValue }
objc_setAssociatedObject(self, &hookingDidMoveToWindowTokenKey, closure, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}

get {
return objc_getAssociatedObject(self, &hookingDidMoveToWindowTokenKey) as? Token
guard let closure = objc_getAssociatedObject(self, &hookingDidMoveToWindowTokenKey) as? () -> Token? else { return nil }
return closure()
}
}

var hookingViewDidDisappearToken: Token? {
set {
objc_setAssociatedObject(self, &hookingViewDidDisappearTokenKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let closure = { return newValue }
objc_setAssociatedObject(self, &hookingViewDidDisappearTokenKey, closure, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}

get {
return objc_getAssociatedObject(self, &hookingViewDidDisappearTokenKey) as? Token
guard let closure = objc_getAssociatedObject(self, &hookingViewDidDisappearTokenKey) as? () -> Token? else { return nil }
return closure()
}
}

var notificationTokens: [NSObjectProtocol] {
set {
objc_setAssociatedObject(self, &notificationTokensKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(self, &notificationTokensKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}

get {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
UIView.detectionInterval = HomeViewController.detectionInterval
UIView.durationThreshold = HomeViewController.durationThreshold
UIView.areaRatioThreshold = HomeViewController.areaRatioThreshold
UIView.alphaThreshold = HomeViewController.alphaThreshold
UIView.redetectOptions = HomeViewController.redetectOptions
ImpressionKitDebug.shared.openLogs()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class CollectionViewDemo2ViewController: UIViewController, UICollectionViewDataS
if state.isImpressed {
print("impressed index: \(index.section), \(index.row)")
}
if let cell = view as? Cell {
if let cell = view.superview as? Cell {
cell.updateUI(state: state)
}
}
Expand Down Expand Up @@ -87,7 +87,8 @@ class CollectionViewDemo2ViewController: UIViewController, UICollectionViewDataS

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
self.group.bind(view: cell, index: indexPath, ignoreDetection: indexPath.section != 1)
cell.contentView.alpha = CGFloat(HomeViewController.alphaInDemo)
self.group.bind(view: cell.contentView, index: indexPath, ignoreDetection: indexPath.section != 1)
cell.index = indexPath.row
cell.updateUI(state: self.group.states[indexPath])
return cell
Expand Down Expand Up @@ -136,7 +137,7 @@ private class Cell: UICollectionViewCell {
self.contentView.backgroundColor = .green
case .inScreen(_):
self.contentView.backgroundColor = .white
UIView.animate(withDuration: TimeInterval(self.durationThreshold ?? UIView.durationThreshold), delay: 0, options: [.curveLinear, .allowUserInteraction], animations: {
UIView.animate(withDuration: TimeInterval(self.contentView.durationThreshold ?? UIView.durationThreshold), delay: 0, options: [.curveLinear, .allowUserInteraction], animations: {
self.contentView.backgroundColor = .red
}, completion: nil)
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class CollectionViewDemoViewController: UIViewController, UICollectionViewDataSo
if state.isImpressed {
print("impressed index: \(index.row)")
}
if let cell = view as? Cell {
if let cell = view.superview as? Cell {
cell.updateUI(state: state)
}
}
Expand Down Expand Up @@ -83,7 +83,8 @@ class CollectionViewDemoViewController: UIViewController, UICollectionViewDataSo

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
self.group.bind(view: cell, index: indexPath)
cell.contentView.alpha = CGFloat(HomeViewController.alphaInDemo)
self.group.bind(view: cell.contentView, index: indexPath)
cell.index = indexPath.row
cell.updateUI(state: self.group.states[indexPath])
return cell
Expand Down Expand Up @@ -132,7 +133,7 @@ private class Cell: UICollectionViewCell {
self.contentView.backgroundColor = .green
case .inScreen(_):
self.contentView.backgroundColor = .white
UIView.animate(withDuration: TimeInterval(self.durationThreshold ?? UIView.durationThreshold), delay: 0, options: [.curveLinear, .allowUserInteraction], animations: {
UIView.animate(withDuration: TimeInterval(self.contentView.durationThreshold ?? UIView.durationThreshold), delay: 0, options: [.curveLinear, .allowUserInteraction], animations: {
self.contentView.backgroundColor = .red
}, completion: nil)
default:
Expand Down
49 changes: 49 additions & 0 deletions ImpressionKitExample/ImpressionKitExample/HomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class HomeViewController: FormViewController {
private static let detectionIntervalKey = "detectionIntervalKey"
private static let durationThresholdKey = "durationThresholdKey"
private static let areaRatioThresholdKey = "areaRatioThresholdKey"
private static let alphaThresholdKey = "alphaThresholdKey"
private static let alphaInDemoKey = "alphaInDemoKey"
private static let redetectOptionsKey = "redetectOptionsKey"

static var detectionInterval: Float {
Expand Down Expand Up @@ -53,6 +55,29 @@ class HomeViewController: FormViewController {
UserDefaults.standard.set(newValue, forKey: areaRatioThresholdKey)
}
}
static var alphaThreshold: Float {
get {
guard UserDefaults.standard.object(forKey: alphaThresholdKey) != nil else {
return UIView.alphaThreshold
}
return UserDefaults.standard.float(forKey: alphaThresholdKey)
}
set {
UIView.alphaThreshold = newValue
UserDefaults.standard.set(newValue, forKey: alphaThresholdKey)
}
}
static var alphaInDemo: Float {
get {
guard UserDefaults.standard.object(forKey: alphaInDemoKey) != nil else {
return 1
}
return UserDefaults.standard.float(forKey: alphaInDemoKey)
}
set {
UserDefaults.standard.set(newValue, forKey: alphaInDemoKey)
}
}
static var redetectOptions: UIView.Redetect {
get {
guard UserDefaults.standard.object(forKey: redetectOptionsKey) != nil else {
Expand Down Expand Up @@ -156,6 +181,28 @@ class HomeViewController: FormViewController {
}.onChange({ (row) in
HomeViewController.areaRatioThreshold = Float((row.value ?? 0) / 100)
})
<<< SliderRow() {
$0.title = "Alpha Threshold"
$0.value = Float(Int(HomeViewController.alphaThreshold * 100))
$0.cell.slider.minimumValue = 1
$0.cell.slider.maximumValue = 100
$0.displayValueFor = {
return "\(Int($0 ?? 0))%"
}
}.onChange({ (row) in
HomeViewController.alphaThreshold = Float((row.value ?? 0) / 100)
})
<<< SliderRow() {
$0.title = "Alpha of views in Demo"
$0.value = Float(Int(HomeViewController.alphaInDemo * 100))
$0.cell.slider.minimumValue = 1
$0.cell.slider.maximumValue = 100
$0.displayValueFor = {
return "\(Int($0 ?? 0))%"
}
}.onChange({ (row) in
HomeViewController.alphaInDemo = Float((row.value ?? 0) / 100)
})
<<< SwitchRow() {
$0.title = "Redetect When Left Screen"
$0.value = HomeViewController.redetectOptions.contains(.leftScreen)
Expand Down Expand Up @@ -203,6 +250,8 @@ class HomeViewController: FormViewController {
HomeViewController.detectionInterval = 0.2
HomeViewController.durationThreshold = 1
HomeViewController.areaRatioThreshold = 0.5
HomeViewController.alphaThreshold = 0.1
HomeViewController.alphaInDemo = 1
HomeViewController.redetectOptions = []
self?.setUpForm()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ private class CellView: UIView {
super.init(frame: CGRect.zero)
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 0.5
self.alpha = CGFloat(HomeViewController.alphaInDemo)

self.label.frame = self.bounds
self.addSubview(self.label)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class TableViewDemoViewController: UIViewController, UITableViewDataSource, UITa
if state.isImpressed {
print("impressed index: \(index.row)")
}
if let cell = view as? Cell {
if let cell = view.superview as? Cell {
cell.updateUI(state: state)
}
}
Expand Down Expand Up @@ -76,7 +76,8 @@ class TableViewDemoViewController: UIViewController, UITableViewDataSource, UITa

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
self.group.bind(view: cell, index: indexPath)
cell.contentView.alpha = CGFloat(HomeViewController.alphaInDemo)
self.group.bind(view: cell.contentView, index: indexPath)
cell.index = indexPath.row
cell.updateUI(state: self.group.states[indexPath])
return cell
Expand Down Expand Up @@ -125,7 +126,7 @@ private class Cell: UITableViewCell {
self.contentView.backgroundColor = .green
case .inScreen(_):
self.contentView.backgroundColor = .white
UIView.animate(withDuration: TimeInterval(self.durationThreshold ?? UIView.durationThreshold), delay: 0, options: [.curveLinear, .allowUserInteraction], animations: {
UIView.animate(withDuration: TimeInterval(self.contentView.durationThreshold ?? UIView.durationThreshold), delay: 0, options: [.curveLinear, .allowUserInteraction], animations: {
self.contentView.backgroundColor = .red
}, completion: nil)
default:
Expand Down
Loading

0 comments on commit 619067f

Please sign in to comment.