Newer Version Available

This content describes an older version of this product. View Latest

Game Zone UI Customization for iOS

After you build the Gamification Mobile SDK for iOS, you can customize the game zone UI components.

Spinner Customization

The game zone UI components are available in the LoyaltyMobileSDK-iOS/SampleApps/MyNTORewards/MyNTORewards/GameZone package.

Customize the spinner components, such as the wheel size, frame width and color, wheel segments, spin button, and more in the FortuneWheelView.swift file. Here are all the parameters you can modify.

1VStack {
2                HStack {
3                    Button {
4                        // Invalidating the timer to avoid unintended navigation because of the timer
5                        timer?.invalidate()
6                        isSpinning ? nil : dismiss()
7                    } label: {
8                        Image("ic-backarrow")
9                    }
10                    .padding(.leading, 20)
11                    .padding(.bottom, 10)
12                    .frame(width: 30, height: 30)
13                    
14                    Spacer()
15                }
16                ZStack {
17                    Color.theme.accent
18                    VStack {
19                        VStack(spacing: 10) {
20                            Text(gameDefinitionModel?.name ?? StringConstants.Gamification.spinaWheelHeaderLabel)
21                                .font(.gameHeaderTitle)
22                            
23                            Text(gameDefinitionModel?.description ?? StringConstants.Gamification.spinaWheelSubHeaderLabel)
24                                .font(.gameHeaderSubTitle)
25                        }
26                        .padding(30)
27                        
28                        Spacer()
29                        ZStack {
30                            // Fortune Wheel Segments
31                            ZStack {
32                                if let colors: [Color] = viewModel.getWheelColors(gameModel: gameDefinitionModel),
33                                   let labels: [LocalizedStringKey] = gameDefinitionModel?.gameRewards.map({LocalizedStringKey($0.name)}) {
34                                    ForEach(0..<colors.count, id: \.self) { index in
35                                        let startAngle = (360.0 / Double(colors.count) * Double(index)) - 90.0
36                                        let endAngle = (360.0 / Double(colors.count) * Double(index + 1)) - 90.0
37                                        WheelSegment(startAngle: startAngle,
38                                                     endAngle: endAngle,
39                                                     color: colors[index],
40                                                     label: labels[index])
41                                    }
42                                }
43                            }
44                            .overlay {
45                                ZStack {
46                                    Circle()
47                                        .strokeBorder(Color.theme.fortuneWheelStrokeColor, lineWidth: 5)
48                                        .frame(width: 316, height: 316)
49                                    Circle()
50                                        .strokeBorder(Color.theme.fortuneWheelSecondaryStrokeColor, lineWidth: 1)
51                                        .frame(width: 307, height: 307)
52                                }
53                            }
54                            .rotationEffect(Angle(degrees: rotationAngle))
55                            .animation(isSpinning ? Animation.easeOut(duration: gameDefinitionModel?.timeoutDuration ?? 15)
56                                .delay(0)
57                                .repeatForever(autoreverses: false) : .default, value: isSpinning)
58                            
59                            // Triangle Arrow Indicator
60                            ZStack {
61                                Triangle()
62                                    .fill(Color.white)
63                                    .frame(width: 70, height: 70)  // Increase these dimensions by the stroke width
64                                    .offset(y: -40)
65                                
66                                Triangle()
67                                    .fill(Color.theme.wheelIndicatorBackground)
68                                    .frame(width: 60, height: 60)
69                                    .offset(y: -35)
70                            }
71                            
72                            // Spin Button
73                            Button {
74                                playGame()
75                            } label: {
76                                userStartedSpinning ?
77                                Text(StringConstants.Gamification.tapSpinButtonLabel).foregroundColor(Color.theme.wheelIndicatorBackground) :
78                                Text(StringConstants.Gamification.tapSpinButtonLabel).foregroundColor(.white)
79                                
80                            }
81                            .frame(width: 70, height: 70)
82                            .background(Color.theme.wheelIndicatorBackground)
83                            .clipShape(Circle())
84                            .overlay(Circle().stroke(Color.white, lineWidth: 5))
85                            .disabled(userStartedSpinning)  // Disable the button when spinning
86                            
87                        }
88                        .frame(width: 300, height: 300)
89                        
90                        Spacer()
91                        
92                        VStack(spacing: 20) {
93                            Text((StringConstants.Gamification.tapSpinaWheeltoPlayLabel))
94                                .font(.gameDescTitle)
95                            Text(StringConstants.Gamification.spinaWheelBodyLabel)
96                                .font(.gameDescText)
97                                .multilineTextAlignment(.center)
98                                .frame(width: 258)
99                        }
100                        Spacer()
101                    }
102                    .foregroundColor(.white)
103                }
104                .cornerRadius(15, corners: [.topLeft, .topRight])
105                .edgesIgnoringSafeArea(.bottom)
106                .fullScreenCover(isPresented: $showAlertForError) {
107                    Spacer()
108                    ProcessingErrorView(message: "Oops! Something went wrong while processing the request. Try again.")
109                    Spacer()
110                    Button {
111                        timer?.invalidate()
112                        isSpinning ? nil : dismiss()
113                    } label: {
114                        Text(StringConstants.Receipts.tryAgainButton)
115                            .frame(maxWidth: .infinity)
116                    }
117                    .buttonStyle(.borderedProminent)
118                    .longFlexibleButtonStyle()
119                }
120            }
121        }

For example, to change the spin wheel outer frame width and height, open the LoyaltyMobileSDK-iOS/SampleApps/MyNTORewards/MyNTORewards/GameZone/Views/FortuneWheel/FortuneWheelView.swift file in Xcode, and replace Circle() frame(width: 316, height: 316) with Circle() frame(width: 300, height: 300).

1overlay {
2                                ZStack {
3                                    Circle()
4                                        .strokeBorder(Color.theme.fortuneWheelStrokeColor, lineWidth: 5)
5                                        .frame(width: 316, height: 316)
6                                    Circle()
7                                        .strokeBorder(Color.theme.fortuneWheelSecondaryStrokeColor, lineWidth: 1)
8                                        .frame(width: 307, height: 307)
9                                }
10                            }

To change the spin button background color to blue, open the LoyaltyMobileSDK-iOS/SampleApps/MyNTORewards/MyNTORewards/Misc/Color.swift file, and inside the ColorTheme structure, replace let wheelIndicatorBackground = Color("wheelIndicatorBackgroundColor") // #23475F with let wheelIndicatorBackground = Color.blue.

Scratch Card Customization

You can customize the scratch card components, such as the box, canvas, wrapper text, and more in the ScratchCanvasView.kt file. Here are all the properties you can customize:

1private var backButton: some View {
2        HStack {
3            Button {
4                // Invalidating the timer to avoid unintended navigation because of the timer
5                timer?.invalidate()
6                dismiss()
7            } label: {
8                Image("ic-backarrow")
9            }
10            .padding(.leading, 20)
11            .padding(.bottom, 10)
12            .frame(width: 30, height: 30)
13            
14            Spacer()
15        }
16    }
17    
18    private var titleView: some View {
19        VStack(spacing: 10) {
20            Text(gameDefinitionModel?.name ?? StringConstants.Gamification.scratchCardTitleLabel)
21                .font(.gameHeaderTitle)
22            Text(gameDefinitionModel?.description ?? StringConstants.Gamification.scratchCardSubTitleLabel)
23                .font(.gameHeaderSubTitle)
24        }
25        .padding(30)
26    }
27    
28    private var loadingView: some View {
29        Text("Loading...")
30            .foregroundStyle(.white)
31            .font(.system(size: 24))
32            .fontWeight(.bold)
33            .frame(width: cardSize.width, height: cardSize.height)
34            .background(Color.theme.accent)
35    }
36    
37    private var rewardText: some View {
38        Text(playGameViewModel.playedGameRewards?.first?.name ?? "--")
39            .font(.largeTitle)
40            .bold()
41            .foregroundStyle(.white)
42            .frame(width: cardSize.width, height: cardSize.height)
43            .background(Color.theme.accent)
44            .zIndex(finishedScratching ? 10 : 0)
45    }
46    
47    private var scratchCardWrapperText: some View {
48        Text(String(repeating: (StringConstants.Gamification.scratchCardLabel), count: 70))
49            .font(Font.scratchText)
50            .multilineTextAlignment(.center)
51            .foregroundColor(Color.theme.scratchCardText)
52            .rotationEffect(Angle(degrees: -45))
53            .mask {
54                Rectangle()
55                    .frame(width: cardSize.width, height: cardSize.height)
56                    .cornerRadius(10)
57                    .opacity(finishedScratching ? 0 : 1)
58            }
59            .opacity(finishedScratching ? 0 : 1)
60    }
61    
62    private var scratchCardContentView: some View {
63        ZStack {
64            // Purple background with postage stamp border
65            DottedBorderRectangle(width: backgroundSize.width,
66                                  height: backgroundSize.height,
67                                  color: Color.theme.accent)
68            
69            // Grey scratch card
70            Rectangle()
71                .fill(Color.theme.scratchCardBackground)
72                .frame(width: cardSize.width, height: cardSize.height)
73                .cornerRadius(10)
74                .opacity(finishedScratching ? 0 : 1)
75            
76            // Text overlay
77            scratchCardWrapperText
78        }
79    }
80    
81    private var scratchCardGame: some View {
82        ScratchCardGame(cursorSize: 30, cardSize: cardSize, gameModel: gameDefinitionModel, onFinish: $finishedScratching) {
83            scratchCardContentView
84        } overlayView: {
85            // Reward text
86            switch playGameViewModel.state {
87            case .loaded:
88                rewardText
89            default:
90                loadingView
91            }
92        }
93        .animation(.linear(duration: 0.5), value: playGameViewModel.state)
94        .environmentObject(playGameViewModel)
95    }
96    
97    private var descriptionView: some View {
98        VStack(spacing: 20) {
99            Text((StringConstants.Gamification.scratchCardBodyLabel))
100                .font(.gameDescText)
101                .multilineTextAlignment(.center)
102                .frame(width: 258)
103        }
104    }
105}

For example, to change the angle of the scratch card wrapper text inside the gray box, open the LoyaltyMobileSDK-iOS/SampleApps/MyNTORewards/MyNTORewards/GameZone/Views/ScrachCard/ScratchCardView.swift file in Xcode, and replace rotationEffect(Angle(degrees: -45)) with rotationEffect(Angle(degrees: -25)) . To change the number of times the text appears inside the gray box, replace Text(String(repeating: (StringConstants.Gamification.scratchCardLabel), count: 70)) with Text(String(repeating: (StringConstants.Gamification.scratchCardLabel), count: 50)) .

1private var scratchCardWrapperText: some View {
2        Text(String(repeating: (StringConstants.Gamification.scratchCardLabel), count: 50))
3            .font(Font.scratchText)
4            .multilineTextAlignment(.center)
5            .foregroundColor(Color.theme.scratchCardText)
6            .rotationEffect(Angle(degrees: -25))
7            .mask {
8                Rectangle()
9                    .frame(width: cardSize.width, height: cardSize.height)
10                    .cornerRadius(10)
11                    .opacity(finishedScratching ? 0 : 1)
12            }
13            .opacity(finishedScratching ? 0 : 1)
14    }