Skip to content

Commit d4b046b

Browse files
author
Evan Greer
committed
Merge branch 'embedded-messaging' into MOB-7053
2 parents 1b697b7 + 1223f53 commit d4b046b

File tree

12 files changed

+599
-150
lines changed

12 files changed

+599
-150
lines changed

sample-apps/swift-sample-app/swift-sample-app.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
ACA3A15120E2F83E00FEF74F /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA3A15020E2F83E00FEF74F /* NotificationService.swift */; };
2222
ACA3A15520E2F83E00FEF74F /* swift-sample-app-notification-extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = ACA3A14E20E2F83D00FEF74F /* swift-sample-app-notification-extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
2323
ACE7624B20FEB2C20040A002 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACE7624A20FEB2C20040A002 /* LoginViewController.swift */; };
24+
E9C60B7B2B3C2061005C4462 /* embeddedmessages.json in Resources */ = {isa = PBXBuildFile; fileRef = E9C60B792B3C2061005C4462 /* embeddedmessages.json */; };
25+
E9C60B7C2B3C2061005C4462 /* EmbeddedMessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C60B7A2B3C2061005C4462 /* EmbeddedMessagesViewController.swift */; };
26+
E9C60B7E2B3C2072005C4462 /* IterableCardViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C60B7D2B3C2072005C4462 /* IterableCardViewCell.swift */; };
2427
/* End PBXBuildFile section */
2528

2629
/* Begin PBXContainerItemProxy section */
@@ -65,6 +68,9 @@
6568
ACA3A16420E2FC7500FEF74F /* swift-sample-app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "swift-sample-app.entitlements"; sourceTree = "<group>"; };
6669
ACE7624A20FEB2C20040A002 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
6770
ACFA148520E3033700AF4A5A /* CoffeeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoffeeType.swift; sourceTree = "<group>"; };
71+
E9C60B792B3C2061005C4462 /* embeddedmessages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = embeddedmessages.json; sourceTree = "<group>"; };
72+
E9C60B7A2B3C2061005C4462 /* EmbeddedMessagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedMessagesViewController.swift; sourceTree = "<group>"; };
73+
E9C60B7D2B3C2072005C4462 /* IterableCardViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IterableCardViewCell.swift; sourceTree = "<group>"; };
6874
/* End PBXFileReference section */
6975

7076
/* Begin PBXFrameworksBuildPhase section */
@@ -128,6 +134,7 @@
128134
ACA3A13720E2F6AF00FEF74F /* swift-sample-app */ = {
129135
isa = PBXGroup;
130136
children = (
137+
E9C60B782B3C2061005C4462 /* EmbeddedMessages */,
131138
AC5ECD9D20E303F50081E1DA /* ViewControllers */,
132139
AC5ECD9B20E3038B0081E1DA /* Deep Link Handling */,
133140
ACFA148620E3034800AF4A5A /* Assets */,
@@ -182,6 +189,16 @@
182189
name = Storyboards;
183190
sourceTree = "<group>";
184191
};
192+
E9C60B782B3C2061005C4462 /* EmbeddedMessages */ = {
193+
isa = PBXGroup;
194+
children = (
195+
E9C60B7D2B3C2072005C4462 /* IterableCardViewCell.swift */,
196+
E9C60B792B3C2061005C4462 /* embeddedmessages.json */,
197+
E9C60B7A2B3C2061005C4462 /* EmbeddedMessagesViewController.swift */,
198+
);
199+
path = EmbeddedMessages;
200+
sourceTree = "<group>";
201+
};
185202
/* End PBXGroup section */
186203

187204
/* Begin PBXNativeTarget section */
@@ -282,6 +299,7 @@
282299
ACA3A14320E2F6B100FEF74F /* LaunchScreen.storyboard in Resources */,
283300
ACA3A14020E2F6B100FEF74F /* Assets.xcassets in Resources */,
284301
ACA3A13E20E2F6AF00FEF74F /* Main.storyboard in Resources */,
302+
E9C60B7B2B3C2061005C4462 /* embeddedmessages.json in Resources */,
285303
);
286304
runOnlyForDeploymentPostprocessing = 0;
287305
};
@@ -299,6 +317,8 @@
299317
isa = PBXSourcesBuildPhase;
300318
buildActionMask = 2147483647;
301319
files = (
320+
E9C60B7C2B3C2061005C4462 /* EmbeddedMessagesViewController.swift in Sources */,
321+
E9C60B7E2B3C2072005C4462 /* IterableCardViewCell.swift in Sources */,
302322
AC1BDF5A20E304CC000010CA /* CoffeeViewController.swift in Sources */,
303323
ACA3A13920E2F6AF00FEF74F /* AppDelegate.swift in Sources */,
304324
AC1BDF5920E304BF000010CA /* DeepLinkHandler.swift in Sources */,

sample-apps/swift-sample-app/swift-sample-app/Base.lproj/Main.storyboard

Lines changed: 140 additions & 20 deletions
Large diffs are not rendered by default.

sample-apps/swift-sample-app/swift-sample-app/CoffeeListTableViewController.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import IterableSDK
1212

1313
class CoffeeListTableViewController: UITableViewController {
1414
@IBOutlet weak var loginOutBarButton: UIBarButtonItem!
15+
@IBOutlet weak var embeddedMessagesBarButton: UIBarButtonItem!
1516

1617
// Set this value to show search.
1718
var searchTerm: String? {
@@ -83,6 +84,12 @@ class CoffeeListTableViewController: UITableViewController {
8384
present(vc, animated: true)
8485
}
8586

87+
@IBAction func embeddedMessagesBarButtonTapped(_: UIBarButtonItem) {
88+
let storyboard = UIStoryboard(name: "Main", bundle: nil)
89+
let vc = storyboard.instantiateViewController(withIdentifier: "EmbeddedMessagesViewController")
90+
present(vc, animated: true)
91+
}
92+
8693
// MARK: - Navigation
8794

8895
override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//
2+
// EmbeddedMessagesViewController.swift
3+
// swift-sample-app
4+
//
5+
// Created by HARDIK MASHRU on 31/10/23.
6+
// Copyright © 2023 Iterable. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import IterableSDK
11+
12+
struct Placement: Codable {
13+
let placementId: Int?
14+
let embeddedMessages: [IterableEmbeddedMessage]
15+
}
16+
17+
struct PlacementsPayload: Codable {
18+
let placements: [Placement]
19+
}
20+
21+
class EmbeddedMessagesViewController: UIViewController {
22+
23+
@IBOutlet weak var doneButton: UIButton!
24+
@IBOutlet weak var syncButton: UIButton!
25+
@IBOutlet weak var embeddedBannerView: UIView!
26+
@IBOutlet weak var carouselCollectionView: UICollectionView!
27+
var cardViews: [IterableEmbeddedMessage] = []
28+
29+
override func viewDidLoad() {
30+
super.viewDidLoad()
31+
embeddedBannerView.isHidden = true
32+
// loadEmeddedMessages() // Load messages using SDK getMessages
33+
loadEmbeddedMessagesFromJSON() // Load messages from static JSON
34+
}
35+
36+
func loadEmbeddedMessagesFromJSON() {
37+
if let jsonData = loadEmbeededMessagesJSON() {
38+
do {
39+
let response = try JSONDecoder().decode(PlacementsPayload.self, from: jsonData)
40+
processEmbeddedMessages(response.placements[0].embeddedMessages)
41+
} catch {
42+
print("Error reading JSON: \(error)")
43+
}
44+
}
45+
46+
}
47+
48+
func loadEmbeededMessagesJSON() -> Data? {
49+
if let path = Bundle.main.path(forResource: "embeddedmessages", ofType: "json") {
50+
do {
51+
let data = try Data(contentsOf: URL(fileURLWithPath: path))
52+
return data
53+
} catch {
54+
print("Error reading JSON file: \(error)")
55+
}
56+
}
57+
return nil
58+
}
59+
60+
func loadEmeddedMessages() {
61+
IterableAPI.embeddedManager.syncMessages {
62+
DispatchQueue.main.async { [self] in
63+
self.processEmbeddedMessages(IterableAPI.embeddedManager.getMessages())
64+
}
65+
}
66+
}
67+
68+
func processEmbeddedMessages(_ messages: [IterableEmbeddedMessage]) {
69+
guard !messages.isEmpty else {
70+
// Handle the case where messages array is empty
71+
return
72+
}
73+
// getMessages fetch embedded messages as shown in embeddedmessages.json response
74+
let bannerView = messages[0]
75+
// We consider rest of messages as carousel of cardviews
76+
cardViews = Array(messages[1..<messages.count])
77+
loadBannerView(bannerView)
78+
embeddedBannerView.isHidden = false
79+
carouselCollectionView.reloadData()
80+
}
81+
82+
func loadCardView(_ embeddedView: IterableEmbeddedView, _ embeddedMessage: IterableEmbeddedMessage) {
83+
embeddedView.iterableEmbeddedViewDelegate = self
84+
embeddedView.primaryBtn.isRoundedSides = true
85+
embeddedView.secondaryBtn.isRoundedSides = true
86+
// We are setting the width of buttons as 140 as per our embedded messages width. You can change as per your need
87+
embeddedView.primaryBtn.widthAnchor.constraint(equalToConstant: 140).isActive = true
88+
embeddedView.secondaryBtn.widthAnchor.constraint(equalToConstant: 140).isActive = true
89+
let config = IterableEmbeddedViewConfig(borderCornerRadius: 10)
90+
embeddedView.configure(message: embeddedMessage, viewType: .card, config: config)
91+
}
92+
93+
func loadBannerView(_ embeddedMessage: IterableEmbeddedMessage) {
94+
let config = IterableEmbeddedViewConfig(borderCornerRadius: 10)
95+
let embeddedView = IterableEmbeddedView(message: embeddedMessage, viewType: .banner, config: config)
96+
embeddedView.iterableEmbeddedViewDelegate = self
97+
embeddedView.primaryBtn.isRoundedSides = true
98+
embeddedView.secondaryBtn.isRoundedSides = true
99+
// We are setting the width of buttons as 140 as per our embedded messages width. You can change as per your need
100+
embeddedView.primaryBtn.widthAnchor.constraint(equalToConstant: 140).isActive = true
101+
embeddedView.secondaryBtn.widthAnchor.constraint(equalToConstant: 140).isActive = true
102+
103+
// You must initialize frame here for the embeddedView
104+
embeddedView.frame = CGRect(x: 0, y: 0, width: embeddedBannerView.frame.width, height: embeddedBannerView.frame.height)
105+
embeddedBannerView.addSubview(embeddedView)
106+
}
107+
108+
@IBAction func doneButtonTapped(_: UIButton) {
109+
presentingViewController?.dismiss(animated: true)
110+
}
111+
112+
@IBAction func syncButtonTapped(_: UIButton) {
113+
loadEmeddedMessages()
114+
}
115+
116+
func openUrl(_ url: String?) {
117+
if let urlString = url, // Replace with your URL string
118+
let url = URL(string: urlString) {
119+
if UIApplication.shared.canOpenURL(url){
120+
UIApplication.shared.open(url)
121+
}
122+
}
123+
}
124+
}
125+
126+
extension EmbeddedMessagesViewController: IterableEmbeddedViewDelegate {
127+
func didPressPrimaryButton(button: UIButton, viewTag: Int, message: IterableSDK.IterableEmbeddedMessage?) {
128+
let buttonData = message?.elements?.buttons?.first
129+
let url = buttonData?.action?.data
130+
openUrl(url)
131+
}
132+
133+
func didPressSecondaryButton(button: UIButton, viewTag: Int, message: IterableSDK.IterableEmbeddedMessage?) {
134+
let buttonData = message?.elements?.buttons?[1]
135+
let url = buttonData?.action?.data
136+
openUrl(url)
137+
}
138+
139+
func didPressBanner(banner: IterableSDK.IterableEmbeddedView, viewTag: Int, message: IterableSDK.IterableEmbeddedMessage?) {
140+
openUrl(message?.elements?.defaultAction?.data)
141+
}
142+
}
143+
144+
extension EmbeddedMessagesViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
145+
146+
func collectionView(_ collectionView: UICollectionView,
147+
numberOfItemsInSection section: Int) -> Int {
148+
return cardViews.count
149+
}
150+
151+
func collectionView(_ collectionView: UICollectionView,
152+
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
153+
154+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "IterableEmbeddedCardViewCell", for: indexPath) as! IterableEmbeddedCardViewCell
155+
let cardView = cardViews[indexPath.row]
156+
loadCardView(cell.embeddedCardView, cardView)
157+
return cell
158+
}
159+
160+
161+
func collectionView(_ collectionView: UICollectionView,
162+
layout collectionViewLayout: UICollectionViewLayout,
163+
sizeForItemAt indexPath: IndexPath) -> CGSize {
164+
return CGSize(width: 350,
165+
height: 400)
166+
}
167+
}
168+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// IterableCardViewCell.swift
3+
// swift-sample-app
4+
//
5+
// Created by HARDIK MASHRU on 31/10/23.
6+
// Copyright © 2023 Iterable. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import IterableSDK
11+
12+
class IterableEmbeddedCardViewCell: UICollectionViewCell {
13+
@IBOutlet weak var embeddedCardView: IterableEmbeddedView!
14+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{
2+
"placements": [
3+
{
4+
"placementId": 0,
5+
"embeddedMessages": [
6+
{
7+
"metadata": {
8+
"messageId": "ZGEzNmMyZTQtNWFiMy00YzUyLWFmZjktZDhhMDZlYjg3ZjA4LzIwNTY3LzgxNjI5MzAvMTA4NDUxNzAvdHJ1ZQ==",
9+
"placementId": 0,
10+
"campaignId": 8162930,
11+
"isProof": true
12+
},
13+
"elements": {
14+
"title": "Join our partner gyms at 40% off",
15+
"body": "Choose from one of our partners and subscribe for 1 year at 40% off",
16+
"mediaUrl": "https://images.pexels.com/photos/1954524/pexels-photo-1954524.jpeg?auto=compress&cs=tinysrgb&w=800",
17+
"buttons": [
18+
{
19+
"id": "gym1",
20+
"title": "Gold's Gym",
21+
"action": {
22+
"type": "openUrl",
23+
"data": "https://goldsgym.in/"
24+
}
25+
},
26+
{
27+
"id": "gym2",
28+
"title": "23 Fitness",
29+
"action": {
30+
"type": "openUrl",
31+
"data": "https://www.23fitnessguam.com/"
32+
}
33+
}
34+
]
35+
}
36+
},
37+
{
38+
"metadata": {
39+
"messageId": "ZGEzNmMyZTQtNWFiMy00YzUyLWFmZjktZDhhMDZlYjg3ZjA4LzIwNTY3LzgxNjI4NDMvMTA4NDUwNjAvdHJ1ZQ==",
40+
"placementId": 0,
41+
"campaignId": 8162843,
42+
"isProof": true
43+
},
44+
"elements": {
45+
"title": "Last chance to grab the deal",
46+
"body": "Visit out website to get the festive deals",
47+
"mediaUrl": "https://athflex.com/cdn/shop/files/pc-banner-for-bag-4_1_1905x.jpg?v=1698474351",
48+
"defaultAction": {
49+
"type": "openUrl",
50+
"data": "https://athflex.com/"
51+
}
52+
}
53+
},
54+
{
55+
"metadata": {
56+
"messageId": "ZGEzNmMyZTQtNWFiMy00YzUyLWFmZjktZDhhMDZlYjg3ZjA4LzIwNTY3LzgxNjI4NjMvMTA4NDUwOTMvdHJ1ZQ==",
57+
"placementId": 0,
58+
"campaignId": 8162863,
59+
"isProof": true
60+
},
61+
"elements": {
62+
"title": "Buy fitness products at huge discounts",
63+
"body": "Visit our website to buy the products",
64+
"mediaUrl": "https://m.media-amazon.com/images/I/71AyxR0yeeL.jpg",
65+
"buttons": [
66+
{
67+
"id": "",
68+
"title": "70% off",
69+
"action": {
70+
"type": "openUrl",
71+
"data": "https://www.vivafitness.net/product_category/commercial-equipment/water-rowers/"
72+
}
73+
}
74+
],
75+
"defaultAction": {
76+
"type": "openUrl",
77+
"data": "https://www.vivafitness.net/product_category/commercial-equipment/commercial-treadmills/"
78+
}
79+
}
80+
},
81+
{
82+
"metadata": {
83+
"messageId": "ZGEzNmMyZTQtNWFiMy00YzUyLWFmZjktZDhhMDZlYjg3ZjA4LzIwNTY3LzgxNjI3NzEvMTA4NDQ5NjIvdHJ1ZQ==",
84+
"placementId": 0,
85+
"campaignId": 8162771,
86+
"isProof": true
87+
},
88+
"elements": {
89+
"title": "Spring Collection",
90+
"body": "Check out our new athleisure collection at 30% off this month!",
91+
"mediaUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSHVMF2KkRm5eWS6LhjQGvxe4KFqzkRbUdAEsAEKvBSy_uU23tRxXH0Ws6HuBjfzf_EsSU&usqp=CAU",
92+
"buttons": [
93+
{
94+
"id": "button1",
95+
"title": "Clearance Sale",
96+
"action": {
97+
"type": "openUrl",
98+
"data": "https://athflex.com/collections/last-chance"
99+
}
100+
}
101+
],
102+
"defaultAction": {
103+
"type": "openUrl",
104+
"data": "https://athflex.com/"
105+
}
106+
}
107+
},
108+
{
109+
"metadata": {
110+
"messageId": "ZGEzNmMyZTQtNWFiMy00YzUyLWFmZjktZDhhMDZlYjg3ZjA4LzIwNTY3LzgxNjc4OTAvMTA4NTEyMzEvdHJ1ZQ==",
111+
"placementId": 0,
112+
"campaignId": 8167890,
113+
"isProof": true
114+
},
115+
"elements": {
116+
"title": "Let's hit the target by running 10kms",
117+
"mediaUrl": "https://media.istockphoto.com/id/1125038961/photo/young-man-running-outdoors-in-morning.jpg?s=612x612&w=0&k=20&c=LVAlQIforg7ZRAF-bOvdvoD_k3ejEeimrWbGq2IA5ak="
118+
}
119+
}
120+
]
121+
}
122+
]
123+
}

0 commit comments

Comments
 (0)