Commit ae46b699 authored by Wei Han's avatar Wei Han

code update

tutorial page, no proper image yet
parent 4ee212f0
{
"images" : [
{
"filename" : "img_tutorial_ai01_3x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "img_tutorial_ai02_3x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "img_tutorial_ai03_3x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "img_tutorial_ma01_3x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "img_tutorial_ma02_3x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "img_tutorial_ma03_3x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#import "NSDate+CalendarHelpers.h" #import "NSDate+CalendarHelpers.h"
#import "AppointmentTimeSlotViewController.h" #import "AppointmentTimeSlotViewController.h"
#import "PullToRefresh.h" #import "PullToRefresh.h"
#import "AppointmentTutorialViewController.h"
@interface AppointmentViewController () <WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate, CalendarViewDelegate> @interface AppointmentViewController () <WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate, CalendarViewDelegate>
...@@ -50,11 +51,6 @@ ...@@ -50,11 +51,6 @@
@property (nonatomic, strong) UIButton *backFooterButton; @property (nonatomic, strong) UIButton *backFooterButton;
@property (nonatomic, strong) UIButton *nextFooterButton; @property (nonatomic, strong) UIButton *nextFooterButton;
// Tutorial overlay
@property (nonatomic, strong) UIView *tutorialOverlay;
@property (nonatomic, strong) UIImageView *tutorialImageView;
@property (nonatomic, strong) UIButton *skipButton;
@end @end
@implementation AppointmentViewController @implementation AppointmentViewController
...@@ -122,8 +118,6 @@ ...@@ -122,8 +118,6 @@
[contentStack.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-16] [contentStack.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-16]
]]; ]];
// Tutorial overlay added last so it’s on top
[self setupTutorialOverlay];
[self setupTermsOverlay]; [self setupTermsOverlay];
// Pull-to-refresh // Pull-to-refresh
...@@ -181,6 +175,23 @@ ...@@ -181,6 +175,23 @@
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor], [self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor], [self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]]; ]];
self.helpButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.helpButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.helpButton setImage:[UIImage systemImageNamed:@"questionmark.circle"] forState:UIControlStateNormal];
self.helpButton.tintColor = UIColor.whiteColor;
[self.helpButton addTarget:self
action:@selector(showTutorial)
forControlEvents:UIControlEventTouchUpInside];
[self.headerBar addSubview:self.helpButton];
[NSLayoutConstraint activateConstraints:@[
[self.helpButton.trailingAnchor constraintEqualToAnchor:self.headerBar.trailingAnchor constant:-16],
[self.helpButton.centerYAnchor constraintEqualToAnchor:self.backButton.centerYAnchor],
[self.helpButton.widthAnchor constraintEqualToConstant:24],
[self.helpButton.heightAnchor constraintEqualToConstant:24],
]];
} }
#pragma mark = Overlay t&c #pragma mark = Overlay t&c
...@@ -422,48 +433,6 @@ ...@@ -422,48 +433,6 @@
]]; ]];
} }
#pragma mark - Tutorial Overlay
- (void)setupTutorialOverlay {
self.tutorialOverlay = [[UIView alloc] init];
self.tutorialOverlay.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
self.tutorialOverlay.hidden = YES;
self.tutorialOverlay.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tutorialOverlay];
[NSLayoutConstraint activateConstraints:@[
[self.tutorialOverlay.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[self.tutorialOverlay.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.tutorialOverlay.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.tutorialOverlay.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
]];
self.tutorialImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"tutorial_step1"]];
self.tutorialImageView.contentMode = UIViewContentModeScaleAspectFit;
self.tutorialImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self.tutorialOverlay addSubview:self.tutorialImageView];
self.skipButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.skipButton setTitle:@"Skip" forState:UIControlStateNormal];
self.skipButton.tintColor = UIColor.whiteColor;
self.skipButton.layer.borderColor = UIColor.whiteColor.CGColor;
self.skipButton.layer.borderWidth = 1.0;
self.skipButton.layer.cornerRadius = 6.0;
self.skipButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.tutorialOverlay addSubview:self.skipButton];
[NSLayoutConstraint activateConstraints:@[
[self.tutorialImageView.centerXAnchor constraintEqualToAnchor:self.tutorialOverlay.centerXAnchor],
[self.tutorialImageView.centerYAnchor constraintEqualToAnchor:self.tutorialOverlay.centerYAnchor],
[self.tutorialImageView.widthAnchor constraintEqualToAnchor:self.tutorialOverlay.widthAnchor multiplier:0.9],
[self.tutorialImageView.heightAnchor constraintEqualToAnchor:self.tutorialOverlay.heightAnchor multiplier:0.7],
[self.skipButton.topAnchor constraintEqualToAnchor:self.tutorialOverlay.safeAreaLayoutGuide.topAnchor constant:16],
[self.skipButton.trailingAnchor constraintEqualToAnchor:self.tutorialOverlay.trailingAnchor constant:-16],
[self.skipButton.widthAnchor constraintEqualToConstant:70],
[self.skipButton.heightAnchor constraintEqualToConstant:32]
]];
}
#pragma mark - api #pragma mark - api
- (void)requestAppointment { - (void)requestAppointment {
[APIClient requestAppointment:^(BOOL success, NSDictionary *data, NSError *error) { [APIClient requestAppointment:^(BOOL success, NSDictionary *data, NSError *error) {
...@@ -758,4 +727,14 @@ ...@@ -758,4 +727,14 @@
}); });
} }
- (void)showTutorial {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Help"
message:@"This would show the tutorial (e.g. step1/step2/step3)."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
AppointmentTutorialViewController *vc = [[AppointmentTutorialViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
@end @end
...@@ -111,29 +111,29 @@ ...@@ -111,29 +111,29 @@
} }
- (void)checkAndRefreshAPIs { - (void)checkAndRefreshAPIs {
NSLog(@"🔍 Checking credentials..."); // NSLog(@"🔍 Checking credentials...");
NSLog(@"ClientID: %@", self.ClientID); // NSLog(@"ClientID: %@", self.ClientID);
NSLog(@"ClientCode: %@", self.ClientCode); // NSLog(@"ClientCode: %@", self.ClientCode);
NSLog(@"user_token: %@", self.user_token); // NSLog(@"user_token: %@", self.user_token);
//
[self showLoadingOverlay:@"Loading..."]; // [self showLoadingOverlay:@"Loading..."];
//
if (![self isReadyToLoadDashboard]) { // if (![self isReadyToLoadDashboard]) {
NSLog(@"⏳ Waiting for all credentials to be ready..."); // NSLog(@"⏳ Waiting for all credentials to be ready...");
return; // return;
} // }
//
if (_isLoadingDashboard) return; // if (_isLoadingDashboard) return;
//
self.isLoadingDashboard = YES; // self.isLoadingDashboard = YES;
//
NSLog(@"✅ All credentials ready. ClientID: %@, ClientCode: %@, user_token: %@", // NSLog(@"✅ All credentials ready. ClientID: %@, ClientCode: %@, user_token: %@",
self.ClientID, self.ClientCode, self.user_token); // self.ClientID, self.ClientCode, self.user_token);
//
//
[APIConfig setAuthTokem:self.user_token]; // [APIConfig setAuthTokem:self.user_token];
[APIConfig setClientId:self.ClientID]; // [APIConfig setClientId:self.ClientID];
// self.ClientCode = @"spt"; self.ClientCode = @"spt";
[self requestCompanyCode]; [self requestCompanyCode];
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
#import "DetailsViewController.h" #import "DetailsViewController.h"
#import "APIClient.h" #import "APIClient.h"
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
#import "IssueTutorialViewController.h" #import "IssuesTutorialViewController.h"
#import "ImageCacheHelper.h" #import "ImageCacheHelper.h"
@interface PlanViewController () @interface PlanViewController ()
...@@ -374,6 +374,7 @@ ...@@ -374,6 +374,7 @@
action:@selector(showTutorial) action:@selector(showTutorial)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.helpButton]; [self.headerView addSubview:self.helpButton];
// 🧩 Title label (project name) — now outside the header // 🧩 Title label (project name) — now outside the header
self.titleLabel = [[UILabel alloc] init]; self.titleLabel = [[UILabel alloc] init];
......
// IssueTutorialViewController.mm
#import "IssueTutorialViewController.h"
@interface IssuesTutorialViewController ()
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *skipButton;
@property (nonatomic, assign) NSInteger currentPage;
@end
@implementation IssuesTutorialViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
[self setupHeader];
}
#pragma mark - Header Setup
- (void)setupHeader {
self.headerView = [[UIView alloc] init];
self.headerView.backgroundColor = [UIColor colorWithRed:0.0 green:0.5 blue:0.6 alpha:1.0];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.headerView];
self.backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"]
forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.backButton];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Add Issue Tutorial";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel];
self.skipButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.skipButton setTitle:@"Skip" forState:UIControlStateNormal];
self.skipButton.tintColor = UIColor.whiteColor;
self.skipButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[self.skipButton addTarget:self
action:@selector(onSkip)
forControlEvents:UIControlEventTouchUpInside];
self.skipButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.skipButton];
// Initially visible on first screen
self.skipButton.hidden = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:48],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:8],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:32],
[self.backButton.heightAnchor constraintEqualToConstant:32],
[self.skipButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-16],
[self.skipButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
]];
}
#pragma mark - Helper
- (void)onBack {
[self.navigationController popViewControllerAnimated:YES];
}
-(void)onSkip {
[self completeTutorial];
}
-(void)completeTutorial {
[self onBack];
}
-(void)updateHeaderButtons {
self.skipButton.hidden = (self.currentPage == 2);
}
- (void)nextButtonTapped {
if (self.currentPage < 2) {
self.currentPage++;
// [self showPage:self.currentPage];
[self updateHeaderButtons];
} else {
[self completeTutorial];
}
}
- (void)backButtonTapped {
if (self.currentPage > 0) {
self.currentPage--;
// [self showPage:self.currentPage];
[self updateHeaderButtons];
}
}
@end
...@@ -415,7 +415,4 @@ heightForHeaderInSection:(NSInteger)section { ...@@ -415,7 +415,4 @@ heightForHeaderInSection:(NSInteger)section {
self.view.userInteractionEnabled = !isLoading; self.view.userInteractionEnabled = !isLoading;
} }
@end @end
// ThirdDetailsViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ThirdPartyDetailsViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// ThirdDetailsViewController.mm
#import "ThirdDetailsViewController.h"
@interface ThirdPartyDetailsViewController ()
@end
@implementation ThirdPartyDetailsViewController
@end
// ThirdPartyViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ThirdPartyViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// ThirdPartyViewController.mm
#import "ThirdPartyViewController.h"
@interface ThirdPartyViewController ()
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UIButton *helpButton;
@end
@implementation ThirdPartyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
}
#pragma mark - Header
- (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
//Header
self.headerBar = [[UIView alloc] init];
self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // #006D8D
[self.view addSubview:self.headerBar];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:44],
]];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Make Appointment";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"xmark"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
for (UIView *sub in @[self.titleLabel, self.backButton]) {
sub.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:sub];
}
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
self.helpButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.helpButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.helpButton setImage:[UIImage systemImageNamed:@"questionmark.circle"] forState:UIControlStateNormal];
self.helpButton.tintColor = UIColor.whiteColor;
[self.helpButton addTarget:self
action:@selector(showTutorial)
forControlEvents:UIControlEventTouchUpInside];
[self.headerBar addSubview:self.helpButton];
[NSLayoutConstraint activateConstraints:@[
[self.helpButton.trailingAnchor constraintEqualToAnchor:self.headerBar.trailingAnchor constant:-16],
[self.helpButton.centerYAnchor constraintEqualToAnchor:self.backButton.centerYAnchor],
[self.helpButton.widthAnchor constraintEqualToConstant:24],
[self.helpButton.heightAnchor constraintEqualToConstant:24],
]];
}
#pragma mark - helpers
- (void)onBack {
[self.navigationController popViewControllerAnimated:YES];
}
@end
// TutorialBubbleView.h
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, BubbleArrowDirection) {
BubbleArrowDirectionUp,
BubbleArrowDirectionDown
};
@interface TutorialBubbleView : UIView
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong) UIButton *prevButton;
@property (nonatomic, strong) UIButton *nextButton;
@property (nonatomic, assign) CGFloat arrowCenterX;
@property (nonatomic, assign) BubbleArrowDirection arrowDirection;
- (void)configureWithText:(NSString *)text
step:(NSInteger)step
total:(NSInteger)total
isFirst:(BOOL)isFirst
isLast:(BOOL)isLast;
@end
// TutorialBubbleView.mm
#import "TutorialBubbleView.h"
static CGFloat const kArrowHeight = 14.0;
static CGFloat const kArrowPadding = 8.0;
@interface TutorialBubbleView ()
@property (nonatomic, strong) UILabel *stepLabel;
@property (nonatomic, strong) UILabel *messageLabel;
@property (nonatomic, strong) UIStackView *buttonStack;
@property (nonatomic, strong) CAShapeLayer *arrowLayer;
@property (nonatomic, strong) NSLayoutConstraint *topConstraint;
@property (nonatomic, strong) NSLayoutConstraint *bottomConstraint;
@end
@implementation TutorialBubbleView
- (instancetype)init {
self = [super initWithFrame:CGRectZero];
if (!self) return nil;
self.backgroundColor = UIColor.whiteColor;
self.layer.cornerRadius = 16;
self.layer.shadowColor = UIColor.blackColor.CGColor;
self.layer.shadowOpacity = 0.15;
self.layer.shadowRadius = 12;
self.layer.shadowOffset = CGSizeMake(0, 6);
self.layer.masksToBounds = NO;
self.translatesAutoresizingMaskIntoConstraints = NO;
CGFloat padding = 18;
// ───────── Step container (pill) ─────────
UIView *stepContainer = [[UIView alloc] init];
stepContainer.translatesAutoresizingMaskIntoConstraints = NO;
stepContainer.backgroundColor =
[[UIColor systemTealColor] colorWithAlphaComponent:0.15];
stepContainer.layer.cornerRadius = 10;
[self addSubview:stepContainer];
self.stepLabel = [[UILabel alloc] init];
self.stepLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold];
self.stepLabel.textColor = UIColor.secondaryLabelColor;
self.stepLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.stepLabel.textAlignment = NSTextAlignmentCenter;
[stepContainer addSubview:self.stepLabel];
// ───────── Message label ─────────
self.messageLabel = [[UILabel alloc] init];
self.messageLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.messageLabel.textColor = UIColor.labelColor;
self.messageLabel.numberOfLines = 0;
self.messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.messageLabel];
UIColor *primaryColor = [UIColor colorWithRed:0.0 green:0.5 blue:0.6 alpha:1.0];
// ───────── Prev Button ─────────
self.prevButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.prevButton setTitle:@"Back" forState:UIControlStateNormal];
self.prevButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
[self.prevButton setTitleColor:primaryColor forState:UIControlStateNormal];
self.prevButton.layer.cornerRadius = 10;
self.prevButton.layer.borderWidth = 0.5;
self.prevButton.layer.borderColor = primaryColor.CGColor;
self.prevButton.contentEdgeInsets = UIEdgeInsetsMake(8, 18, 8, 18);
self.prevButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.prevButton];
// ───────── Next Button ─────────
self.nextButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.nextButton setTitle:@"Next" forState:UIControlStateNormal];
self.nextButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
self.nextButton.backgroundColor = primaryColor;
[self.nextButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
self.nextButton.layer.cornerRadius = 10;
self.nextButton.contentEdgeInsets = UIEdgeInsetsMake(8, 18, 8, 18);
self.nextButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.nextButton];
[NSLayoutConstraint activateConstraints:@[
[self.heightAnchor constraintGreaterThanOrEqualToConstant:120],
// Step pill
[stepContainer.topAnchor constraintEqualToAnchor:self.topAnchor
constant:padding],
[stepContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor
constant:padding],
[self.stepLabel.topAnchor constraintEqualToAnchor:stepContainer.topAnchor constant:4],
[self.stepLabel.bottomAnchor constraintEqualToAnchor:stepContainer.bottomAnchor constant:-4],
[self.stepLabel.leadingAnchor constraintEqualToAnchor:stepContainer.leadingAnchor constant:10],
[self.stepLabel.trailingAnchor constraintEqualToAnchor:stepContainer.trailingAnchor constant:-10],
// Message
[self.messageLabel.topAnchor constraintEqualToAnchor:stepContainer.bottomAnchor constant:8],
[self.messageLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:padding],
[self.messageLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-padding],
//Prev Button
[self.prevButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:12],
[self.prevButton.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-12],
[self.prevButton.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-12],
[self.prevButton.topAnchor constraintEqualToAnchor:self.messageLabel.bottomAnchor constant:16],
//Next Button
[self.nextButton.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:12],
[self.nextButton.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-12],
[self.nextButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-12],
[self.nextButton.topAnchor constraintEqualToAnchor:self.messageLabel.bottomAnchor constant:16],
]];
// ───────── Arrow ─────────
self.arrowLayer = [CAShapeLayer layer];
self.arrowLayer.fillColor = UIColor.whiteColor.CGColor;
[self.layer addSublayer:self.arrowLayer];
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat arrowWidth = 18.0;
CGFloat arrowHeight = kArrowHeight;
CGFloat centerX = (self.arrowCenterX > 0) ? self.arrowCenterX : CGRectGetMidX(self.bounds);
UIBezierPath *path = [UIBezierPath bezierPath];
if (self.arrowDirection == BubbleArrowDirectionUp) {
self.arrowLayer.frame = CGRectMake(0, -arrowHeight, self.bounds.size.width, self.bounds.size.height + arrowHeight);
// Arrow tip at the very top
CGPoint tip = CGPointMake(centerX, 0);
CGPoint baseLeft = CGPointMake(centerX - arrowWidth / 2, arrowHeight);
CGPoint baseRight = CGPointMake(centerX + arrowWidth / 2, arrowHeight);
[path moveToPoint:baseLeft];
[path addLineToPoint:baseRight];
[path addLineToPoint:tip];
} else { // BubbleArrowDirectionDown
self.arrowLayer.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height - arrowHeight);
CGFloat bubbleHeight = self.bounds.size.height;
// Arrow tip at the very bottom
CGPoint tip = CGPointMake(centerX, bubbleHeight + arrowHeight);
CGPoint baseLeft = CGPointMake(centerX - arrowWidth / 2, bubbleHeight);
CGPoint baseRight = CGPointMake(centerX + arrowWidth / 2, bubbleHeight);
[path moveToPoint:baseLeft];
[path addLineToPoint:baseRight];
[path addLineToPoint:tip];
}
[path closePath];
self.arrowLayer.path = path.CGPath;
}
- (void)configureWithText:(NSString *)text
step:(NSInteger)step
total:(NSInteger)total
isFirst:(BOOL)isFirst
isLast:(BOOL)isLast {
self.stepLabel.text = [NSString stringWithFormat:@"Step %ld",
(long)(step + 1)];
self.messageLabel.text = text;
self.prevButton.hidden = isFirst;
[self.nextButton setTitle:(isLast ? @"Done" : @"Next")
forState:UIControlStateNormal];
}
- (void)setArrowDirection:(BubbleArrowDirection)arrowDirection {
_arrowDirection = arrowDirection;
CGFloat padding = 20;
if (arrowDirection == BubbleArrowDirectionUp) {
self.topConstraint.constant = padding + kArrowHeight;
self.bottomConstraint.constant = -padding;
} else {
self.topConstraint.constant = padding;
self.bottomConstraint.constant = -(padding + kArrowHeight);
}
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (CGSize)intrinsicContentSize {
CGSize size = [super intrinsicContentSize];
return CGSizeMake(size.width, size.height + kArrowHeight);
}
@end
// TutorialStep.h
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@interface TutorialStep : NSObject
@property (nonatomic, copy) NSString *text;
@property (nonatomic, assign) CGPoint anchorPoint;
@property (nonatomic, assign) BOOL bubbleAbove;
@end
// TutorialStep.mm
#import "TutorialStep.h"
@implementation TutorialStep
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment