Skip to content

Commit 3ff557f

Browse files
Merge pull request #413 from Iterable/bug/mob-2239-in-app-animation
[MOB-2239] - In app animation bug fix
2 parents e323816 + 6295ec8 commit 3ff557f

File tree

8 files changed

+188
-110
lines changed

8 files changed

+188
-110
lines changed

swift-sdk/Internal/InAppCalculations.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,58 @@ import Foundation
88
import UIKit
99

1010
struct InAppCalculations {
11+
struct AnimationInput {
12+
let position: ViewPosition
13+
let isModal: Bool
14+
let shouldAnimate: Bool
15+
let location: IterableMessageLocation
16+
let safeAreaInsets: UIEdgeInsets
17+
let backgroundColor: UIColor?
18+
}
19+
20+
struct AnimationDetail {
21+
let initial: AnimationParam
22+
let final: AnimationParam
23+
}
24+
25+
struct AnimationParam {
26+
let position: ViewPosition
27+
let alpha: CGFloat
28+
let bgColor: UIColor
29+
}
30+
31+
static func calculateAnimationDetail(animationInput input: AnimationInput) -> AnimationDetail? {
32+
guard input.isModal == true else {
33+
return nil
34+
}
35+
36+
if input.shouldAnimate {
37+
let startPosition = calculateAnimationStartPosition(for: input.position,
38+
location: input.location,
39+
safeAreaInsets: input.safeAreaInsets)
40+
let startAlpha = calculateAnimationStartAlpha(location: input.location)
41+
42+
let initialParam = AnimationParam(position: startPosition,
43+
alpha: startAlpha,
44+
bgColor: UIColor.clear)
45+
let finalBgColor = finalViewBackgroundColor(bgColor: input.backgroundColor, isModal: input.isModal)
46+
let finalParam = AnimationParam(position: input.position,
47+
alpha: 1.0,
48+
bgColor: finalBgColor)
49+
return AnimationDetail(initial: initialParam,
50+
final: finalParam)
51+
} else if let bgColor = input.backgroundColor {
52+
return AnimationDetail(initial: AnimationParam(position: input.position, alpha: 1.0, bgColor: UIColor.clear),
53+
final: AnimationParam(position: input.position, alpha: 1.0, bgColor: bgColor))
54+
} else {
55+
return nil
56+
}
57+
}
58+
59+
static func swapAnimation(animationDetail: AnimationDetail) -> AnimationDetail {
60+
AnimationDetail(initial: animationDetail.final, final: animationDetail.initial)
61+
}
62+
1163
static func calculateAnimationStartPosition(for position: ViewPosition,
1264
location: IterableMessageLocation,
1365
safeAreaInsets: UIEdgeInsets) -> ViewPosition {
@@ -95,4 +147,36 @@ struct InAppCalculations {
95147

96148
return position
97149
}
150+
151+
static func createDismisser(for viewController: UIViewController, isModal: Bool, isInboxMessage: Bool) -> () -> Void {
152+
guard isModal else {
153+
return { [weak viewController] in
154+
viewController?.navigationController?.popViewController(animated: true)
155+
}
156+
}
157+
158+
return { [weak viewController] in
159+
viewController?.dismiss(animated: isInboxMessage)
160+
}
161+
}
162+
163+
static func initialViewBackgroundColor(isModal: Bool) -> UIColor {
164+
isModal ? UIColor.clear : systemBackgroundColor
165+
}
166+
167+
static func finalViewBackgroundColor(bgColor: UIColor?, isModal: Bool) -> UIColor {
168+
if isModal {
169+
return bgColor ?? UIColor.clear
170+
} else {
171+
return systemBackgroundColor
172+
}
173+
}
174+
175+
static var systemBackgroundColor: UIColor {
176+
if #available(iOS 14, *) {
177+
return UIColor.systemBackground
178+
} else {
179+
return UIColor.white
180+
}
181+
}
98182
}

swift-sdk/Internal/InAppContentParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ extension HtmlContentParser: ContentFromJsonParser {
177177
let edgeInsets = getPadding(fromInAppSettings: inAppDisplaySettings)
178178

179179
let shouldAnimate = inAppDisplaySettings.map(Self.parseShouldAnimate(fromInAppSettings:)) ?? false
180-
let backgroundColor = inAppDisplaySettings.flatMap(Self.parseBackgroundColor(fromInAppSettings:)) ?? IterableHtmlInAppContent.defaultBackgroundColor()
180+
let backgroundColor = inAppDisplaySettings.flatMap(Self.parseBackgroundColor(fromInAppSettings:))
181181

182182
return .success(content: IterableHtmlInAppContent(edgeInsets: edgeInsets,
183183
html: html,

swift-sdk/Internal/InAppDisplayer.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,6 @@ class InAppDisplayer: InAppDisplayerProtocol {
6464

6565
topViewController.definesPresentationContext = true
6666

67-
// htmlMessageVC.view triggers WKWebView's loadView() to start loading the HTML.
68-
// just make sure that's triggered for the InAppPresenter work correctly
69-
if #available(iOS 14, *) {
70-
htmlMessageVC.view.backgroundColor = UIColor.systemBackground.withAlphaComponent(0.0)
71-
} else {
72-
htmlMessageVC.view.backgroundColor = UIColor.white.withAlphaComponent(0.0)
73-
}
74-
7567
htmlMessageVC.modalPresentationStyle = .overFullScreen
7668

7769
let presenter = InAppPresenter(topViewController: topViewController, htmlMessageViewController: htmlMessageVC)

swift-sdk/Internal/InAppPersistence.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ extension IterableHtmlInAppContent: Codable {
158158
let edgeInsets = (try? container.decode(UIEdgeInsets.self, forKey: .edgeInsets)) ?? .zero
159159
let html = (try? container.decode(String.self, forKey: .html)) ?? ""
160160
let shouldAnimate = (try? container.decode(Bool.self, forKey: .shouldAnimate)) ?? false
161-
let backgroundColor = (try? container.decode(CodableColor.self, forKey: .bgColor)).map(CodableColor.uiColorFromCodableColor(_:)) ?? IterableHtmlInAppContent.defaultBackgroundColor()
161+
let backgroundColor = (try? container.decode(CodableColor.self, forKey: .bgColor)).map(CodableColor.uiColorFromCodableColor(_:))
162162

163163
return IterableHtmlInAppContent(edgeInsets: edgeInsets,
164164
html: html,
@@ -172,7 +172,9 @@ extension IterableHtmlInAppContent: Codable {
172172
try? container.encode(htmlContent.edgeInsets, forKey: .edgeInsets)
173173
try? container.encode(htmlContent.html, forKey: .html)
174174
try? container.encode(htmlContent.shouldAnimate, forKey: .shouldAnimate)
175-
try? container.encode(CodableColor.codableColorFromUIColor(htmlContent.backgroundColor), forKey: .bgColor)
175+
if let backgroundColor = htmlContent.backgroundColor {
176+
try? container.encode(CodableColor.codableColorFromUIColor(backgroundColor), forKey: .bgColor)
177+
}
176178
}
177179

178180
public convenience init(from decoder: Decoder) {

swift-sdk/Internal/IterableHtmlMessageViewController.swift

Lines changed: 62 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ class IterableHtmlMessageViewController: UIViewController {
4242
.map { $0.shouldAnimate } ?? false
4343
}
4444

45-
var backgroundColor: UIColor {
45+
var backgroundColor: UIColor? {
4646
messageMetadata
4747
.flatMap { $0.message.content as? IterableHtmlInAppContent }
48-
.map { $0.backgroundColor } ?? IterableHtmlInAppContent.defaultBackgroundColor()
48+
.flatMap { $0.backgroundColor }
4949
}
5050

5151
var location: IterableMessageLocation {
@@ -85,15 +85,7 @@ class IterableHtmlMessageViewController: UIViewController {
8585

8686
location = parameters.location
8787

88-
if parameters.isModal {
89-
view.backgroundColor = UIColor.clear
90-
} else {
91-
if #available(iOS 14, *) {
92-
view.backgroundColor = UIColor.systemBackground
93-
} else {
94-
view.backgroundColor = UIColor.white
95-
}
96-
}
88+
view.backgroundColor = InAppCalculations.initialViewBackgroundColor(isModal: parameters.isModal)
9789

9890
webView.set(position: ViewPosition(width: view.frame.width, height: view.frame.height, center: view.center))
9991
webView.loadHTMLString(parameters.html, baseURL: URL(string: ""))
@@ -119,8 +111,8 @@ class IterableHtmlMessageViewController: UIViewController {
119111

120112
override func viewDidLayoutSubviews() {
121113
super.viewDidLayoutSubviews()
122-
123-
resizeWebView()
114+
115+
resizeWebView(animate: false)
124116
}
125117

126118
override open func viewWillDisappear(_ animated: Bool) {
@@ -174,23 +166,8 @@ class IterableHtmlMessageViewController: UIViewController {
174166
return webView as WebViewProtocol
175167
}
176168

177-
private static func trackClickOnDismiss(internalAPI: IterableAPIInternal?,
178-
params: Parameters,
179-
futureClickedURL: Promise<URL, IterableError>,
180-
withURL url: URL,
181-
andDestinationURL destinationURL: String) {
182-
ITBInfo()
183-
futureClickedURL.resolve(with: url)
184-
if let messageMetadata = params.messageMetadata {
185-
internalAPI?.trackInAppClick(messageMetadata.message,
186-
location: messageMetadata.location,
187-
inboxSessionId: params.inboxSessionId,
188-
clickedUrl: destinationURL)
189-
}
190-
}
191-
192169
/// Resizes the webview based upon the insetPadding, height etc
193-
private func resizeWebView() {
170+
private func resizeWebView(animate: Bool) {
194171
let parentPosition = ViewPosition(width: view.bounds.width,
195172
height: view.bounds.height,
196173
center: view.center)
@@ -201,52 +178,48 @@ class IterableHtmlMessageViewController: UIViewController {
201178
paddingRight: parameters.padding.right,
202179
location: location)
203180
.onSuccess { [weak self] position in
204-
if self?.parameters.isModal == true && self?.parameters.shouldAnimate == true {
205-
self?.animateWhileEntering(position: position)
181+
if animate {
182+
self?.animateWhileEntering(position)
206183
} else {
207184
self?.webView.set(position: position)
208185
}
209186
}
210187
}
211188

212-
private func animateWhileEntering(position: ViewPosition) {
213-
Self.animate(duration: parameters.animationDuration) { [weak self] in
214-
self?.setInitialValuesForAnimation(position: position)
215-
} finalValues: { [weak self] in
216-
self?.setFinalValuesForAnimation(position: position)
217-
}
189+
private func animateWhileEntering(_ position: ViewPosition) {
190+
ITBInfo()
191+
createAnimationDetail(withPosition: position).map { applyAnimation(animationDetail: $0) } ?? (webView.set(position: position))
218192
}
219-
220-
private func setInitialValuesForAnimation(position: ViewPosition) {
221-
view.backgroundColor = UIColor.clear
222-
let startPosition = InAppCalculations.calculateAnimationStartPosition(for: position,
223-
location: location,
224-
safeAreaInsets: InAppCalculations.safeAreaInsets(for: view))
225-
let startAlpha = InAppCalculations.calculateAnimationStartAlpha(location: location)
226-
webView.set(position: startPosition)
227-
webView.view.alpha = startAlpha
193+
194+
private func animateWhileLeaving(_ position: ViewPosition) {
195+
let animation = createAnimationDetail(withPosition: position).map(InAppCalculations.swapAnimation(animationDetail:))
196+
let dismisser = InAppCalculations.createDismisser(for: self,
197+
isModal: parameters.isModal,
198+
isInboxMessage: parameters.messageMetadata?.location == .inbox)
199+
animation.map { applyAnimation(animationDetail: $0, completion: dismisser) } ?? (dismisser())
228200
}
229-
230-
private func setFinalValuesForAnimation(position: ViewPosition) {
231-
view.backgroundColor = parameters.backgroundColor
232-
webView.set(position: position)
233-
webView.view.alpha = 1.0
201+
202+
private func createAnimationDetail(withPosition position: ViewPosition) -> InAppCalculations.AnimationDetail? {
203+
let input = InAppCalculations.AnimationInput(position: position,
204+
isModal: parameters.isModal,
205+
shouldAnimate: parameters.shouldAnimate,
206+
location: location,
207+
safeAreaInsets: InAppCalculations.safeAreaInsets(for: view),
208+
backgroundColor: parameters.backgroundColor)
209+
return InAppCalculations.calculateAnimationDetail(animationInput: input)
234210
}
235211

236-
private func animateWhileLeaving(position: ViewPosition, url: URL, destinationUrl: String) {
237-
ITBInfo()
212+
private func applyAnimation(animationDetail: InAppCalculations.AnimationDetail, completion: (() -> Void)? = nil) {
238213
Self.animate(duration: parameters.animationDuration) { [weak self] in
239-
self?.setFinalValuesForAnimation(position: position)
214+
self?.webView.set(position: animationDetail.initial.position)
215+
self?.webView.view.alpha = animationDetail.initial.alpha
216+
self?.view.backgroundColor = animationDetail.initial.bgColor
240217
} finalValues: { [weak self] in
241-
self?.setInitialValuesForAnimation(position: position)
242-
} completion: { [weak self, weak internalApi = internalAPI, futureClickedURL = futureClickedURL, parameters = parameters] in
243-
self?.dismiss(animated: false, completion: {
244-
Self.trackClickOnDismiss(internalAPI: internalApi,
245-
params: parameters,
246-
futureClickedURL: futureClickedURL,
247-
withURL: url,
248-
andDestinationURL: destinationUrl)
249-
})
218+
self?.webView.set(position: animationDetail.final.position)
219+
self?.webView.view.alpha = animationDetail.final.alpha
220+
self?.view.backgroundColor = animationDetail.final.bgColor
221+
} completion: {
222+
completion?()
250223
}
251224
}
252225

@@ -288,7 +261,7 @@ class IterableHtmlMessageViewController: UIViewController {
288261
extension IterableHtmlMessageViewController: WKNavigationDelegate {
289262
func webView(_: WKWebView, didFinish _: WKNavigation!) {
290263
ITBInfo()
291-
resizeWebView()
264+
resizeWebView(animate: true)
292265
presenter?.webViewDidFinish()
293266
}
294267

@@ -312,30 +285,31 @@ extension IterableHtmlMessageViewController: WKNavigationDelegate {
312285

313286
linkClicked = true
314287
clickedLink = destinationUrl
315-
316-
if parameters.isModal {
317-
if parameters.shouldAnimate {
318-
animateWhileLeaving(position: webView.position, url: url, destinationUrl: destinationUrl)
319-
} else {
320-
let animated = parameters.messageMetadata?.location == .inbox
321-
dismiss(animated: animated) { [weak internalAPI, futureClickedURL, parameters, url, destinationUrl] in
322-
Self.trackClickOnDismiss(internalAPI: internalAPI,
323-
params: parameters,
324-
futureClickedURL: futureClickedURL,
325-
withURL: url,
326-
andDestinationURL: destinationUrl)
327-
}
328-
}
329-
} else {
330-
Self.trackClickOnDismiss(internalAPI: internalAPI,
331-
params: parameters,
332-
futureClickedURL: futureClickedURL,
333-
withURL: url,
334-
andDestinationURL: destinationUrl)
335-
336-
navigationController?.popViewController(animated: true)
337-
}
338-
288+
289+
Self.trackClickOnDismiss(internalAPI: internalAPI,
290+
params: parameters,
291+
futureClickedURL: futureClickedURL,
292+
withURL: url,
293+
andDestinationURL: destinationUrl)
294+
295+
animateWhileLeaving(webView.position)
296+
339297
decisionHandler(.cancel)
340298
}
299+
300+
private static func trackClickOnDismiss(internalAPI: IterableAPIInternal?,
301+
params: Parameters,
302+
futureClickedURL: Promise<URL, IterableError>,
303+
withURL url: URL,
304+
andDestinationURL destinationURL: String) {
305+
ITBInfo()
306+
futureClickedURL.resolve(with: url)
307+
if let messageMetadata = params.messageMetadata {
308+
internalAPI?.trackInAppClick(messageMetadata.message,
309+
location: messageMetadata.location,
310+
inboxSessionId: params.inboxSessionId,
311+
clickedUrl: destinationURL)
312+
}
313+
}
314+
341315
}

swift-sdk/IterableMessaging.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,19 @@ public extension Notification.Name {
9292
public let edgeInsets: UIEdgeInsets
9393
public let html: String
9494
public let shouldAnimate: Bool
95-
public let backgroundColor: UIColor
95+
public let backgroundColor: UIColor?
9696

9797
// MARK: - Private/Internal
9898

9999
init(edgeInsets: UIEdgeInsets,
100100
html: String,
101101
shouldAnimate: Bool = false,
102-
backgroundColor: UIColor = IterableHtmlInAppContent.defaultBackgroundColor()) {
102+
backgroundColor: UIColor? = nil) {
103103
self.edgeInsets = edgeInsets
104104
self.html = html
105105
self.shouldAnimate = shouldAnimate
106106
self.backgroundColor = backgroundColor
107107
}
108-
109-
static func defaultBackgroundColor() -> UIColor {
110-
if #available(iOS 14, *) {
111-
return UIColor.systemBackground.withAlphaComponent(0.0)
112-
} else {
113-
return UIColor.white.withAlphaComponent(0.0)
114-
}
115-
}
116108
}
117109

118110
@objcMembers public final class IterableInboxMetadata: NSObject {

0 commit comments

Comments
 (0)