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
}
}
// AppointmentTutorialViewController.mm
#import "AppointmentTutorialViewController.h"
#import "TutorialBubbleView.h"
#import "TutorialStep.h"
@interface AppointmentTutorialViewController ()
......@@ -8,6 +10,12 @@
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *skipButton;
@property (nonatomic, assign) NSInteger currentPage;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIImageView *tutorialImageView;
@property (nonatomic, strong) NSArray<UIImage *> *tutorialImages;
@property (nonatomic, strong) NSArray<TutorialStep *> *steps;
@property (nonatomic, strong) TutorialBubbleView *bubbleView;
@property (nonatomic, assign) BOOL didLayoutInitialPage;
@end
......@@ -16,8 +24,27 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
self.didLayoutInitialPage = NO;
[self setupHeader];
[self setupContent];
[self setupTutorialData];
[self setupBubble];
[self.view bringSubviewToFront:self.headerView];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (!self.didLayoutInitialPage) {
self.didLayoutInitialPage = YES;
// showPage now that layout is ready
[self showPage:self.currentPage];
} else {
// update bubble position for rotation / layout changes
[self updateBubblePosition];
}
}
#pragma mark - Header Setup
......@@ -38,7 +65,7 @@
[self.headerView addSubview:self.backButton];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Add Appointment Tutorial";
self.titleLabel.text = @"Add Appintment Tutorial";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
......@@ -47,6 +74,9 @@
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 = 0.5;
self.skipButton.layer.cornerRadius = 14;
self.skipButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[self.skipButton addTarget:self
action:@selector(onSkip)
......@@ -61,12 +91,12 @@
[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.headerView.heightAnchor constraintEqualToConstant:44],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:8],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:32],
[self.backButton.heightAnchor constraintEqualToConstant:32],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.skipButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-16],
[self.skipButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
......@@ -76,9 +106,97 @@
]];
}
#pragma mark - Content
- (void)setupContent {
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.contentView];
self.tutorialImageView = [[UIImageView alloc] init];
self.tutorialImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.tutorialImageView.contentMode = UIViewContentModeScaleAspectFill;
self.tutorialImageView.clipsToBounds = YES;
[self.contentView addSubview:self.tutorialImageView];
[NSLayoutConstraint activateConstraints:@[
[self.contentView.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
[self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.tutorialImageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.tutorialImageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.tutorialImageView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.tutorialImageView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor],
]];
}
#pragma mark - Tutorial Data
- (void)setupTutorialData {
// Using black placeholder images for testing
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UIImage *img1 = [UIImage imageNamed:@"img_tutorial_ma01_3x"
inBundle:bundle
compatibleWithTraitCollection:nil];
UIImage *img2 = [UIImage imageNamed:@"img_tutorial_ma02_3x"
inBundle:bundle
compatibleWithTraitCollection:nil];
UIImage *img3 = [UIImage imageNamed:@"img_tutorial_ma03_3x"
inBundle:bundle
compatibleWithTraitCollection:nil];
self.tutorialImages = @[img1, img2, img3];
self.steps = @[
({
TutorialStep *s = [TutorialStep new];
s.text = @"Select appointment date.";
s.anchorPoint = CGPointMake(0.63, 0.5);
s.bubbleAbove = NO;
s;
}),
({
TutorialStep *s = [TutorialStep new];
s.text = @"Select appointment time slot.";
s.anchorPoint = CGPointMake(0.4, 0.32);
s.bubbleAbove = NO;
s;
}),
({
TutorialStep *s = [TutorialStep new];
s.text = @"Select \"Confirm\" button to confirm appointment details.";
s.anchorPoint = CGPointMake(0.6, 0.86);
s.bubbleAbove = YES;
s;
})
];
self.currentPage = 0;
}
#pragma mark - Bubble
- (void)setupBubble {
self.bubbleView = [[TutorialBubbleView alloc] init];
self.bubbleView.translatesAutoresizingMaskIntoConstraints = NO;
[self.bubbleView.prevButton addTarget:self
action:@selector(backButtonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.bubbleView.nextButton addTarget:self
action:@selector(nextButtonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.bubbleView];
// Fixed width; height will auto-size
[self.bubbleView.widthAnchor constraintEqualToConstant:260].active = YES;
}
#pragma mark - Helper
- (void)onBack {
[self.navigationController popViewControllerAnimated:YES];
NSLog(@"on back triggered");
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)onSkip {
......@@ -90,14 +208,25 @@
}
-(void)updateHeaderButtons {
self.skipButton.hidden = (self.currentPage == 2);
self.skipButton.hidden = (self.currentPage == self.steps.count - 1);
}
- (void)showPage:(NSInteger)page {
if (page < 0 || page >= self.tutorialImages.count) return;
self.tutorialImageView.image = self.tutorialImages[page];
[self updateHeaderButtons];
[self updateBubbleContent];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateBubblePosition];
});
}
- (void)nextButtonTapped {
if (self.currentPage < 2) {
if (self.currentPage < self.steps.count - 1) {
self.currentPage++;
// [self showPage:self.currentPage];
[self updateHeaderButtons];
[self showPage:self.currentPage];
} else {
[self completeTutorial];
}
......@@ -106,11 +235,81 @@
- (void)backButtonTapped {
if (self.currentPage > 0) {
self.currentPage--;
// [self showPage:self.currentPage];
[self updateHeaderButtons];
[self showPage:self.currentPage];
}
}
- (CGRect)imageFrameInImageView:(UIImageView *)imageView {
if (!imageView.image) return CGRectZero;
@end
CGSize imageSize = imageView.image.size;
CGSize viewSize = imageView.bounds.size;
CGFloat imageRatio = imageSize.width / imageSize.height;
CGFloat viewRatio = viewSize.width / viewSize.height;
CGSize drawSize;
if (imageRatio > viewRatio) {
// Image is wider → height fills, width overflows
drawSize.height = viewSize.height;
drawSize.width = viewSize.height * imageRatio;
} else {
// Image is taller → width fills, height overflows
drawSize.width = viewSize.width;
drawSize.height = viewSize.width / imageRatio;
}
CGFloat x = (viewSize.width - drawSize.width) / 2.0;
CGFloat y = (viewSize.height - drawSize.height) / 2.0;
return CGRectMake(x, y, drawSize.width, drawSize.height);
}
- (CGPoint)screenPointForCurrentStep {
TutorialStep *step = self.steps[self.currentPage];
CGRect imageFrame = [self imageFrameInImageView:self.tutorialImageView];
CGFloat x = imageFrame.origin.x + step.anchorPoint.x * imageFrame.size.width;
CGFloat y = imageFrame.origin.y + step.anchorPoint.y * imageFrame.size.height;
return CGPointMake(x, y);
}
- (void)updateBubblePosition {
if (!self.bubbleView || !self.tutorialImageView.image) return;
// Make sure bubble layout is up-to-date
[self.bubbleView layoutIfNeeded];
TutorialStep *step = self.steps[self.currentPage];
CGPoint anchor = [self screenPointForCurrentStep];
self.bubbleView.arrowDirection =
step.bubbleAbove ? BubbleArrowDirectionDown : BubbleArrowDirectionUp;
CGFloat bubbleHeight =
[self.bubbleView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGFloat bubbleWidth = 260;
CGFloat x = anchor.x - bubbleWidth / 2.0;
CGFloat y = step.bubbleAbove
? anchor.y - bubbleHeight - 12
: anchor.y + 12;
self.bubbleView.frame = CGRectMake(x, y, bubbleWidth, bubbleHeight);
}
- (void)updateBubbleContent {
TutorialStep *step = self.steps[self.currentPage];
[self.bubbleView configureWithText:step.text
step:self.currentPage
total:self.steps.count
isFirst:self.currentPage == 0
isLast:self.currentPage == self.steps.count - 1];
self.bubbleView.prevButton.hidden = (self.currentPage == 0);
}
@end
......@@ -8,6 +8,7 @@
#import "NSDate+CalendarHelpers.h"
#import "AppointmentTimeSlotViewController.h"
#import "PullToRefresh.h"
#import "AppointmentTutorialViewController.h"
@interface AppointmentViewController () <WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate, CalendarViewDelegate>
......@@ -50,11 +51,6 @@
@property (nonatomic, strong) UIButton *backFooterButton;
@property (nonatomic, strong) UIButton *nextFooterButton;
// Tutorial overlay
@property (nonatomic, strong) UIView *tutorialOverlay;
@property (nonatomic, strong) UIImageView *tutorialImageView;
@property (nonatomic, strong) UIButton *skipButton;
@end
@implementation AppointmentViewController
......@@ -122,8 +118,6 @@
[contentStack.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-16]
]];
// Tutorial overlay added last so it’s on top
[self setupTutorialOverlay];
[self setupTermsOverlay];
// Pull-to-refresh
......@@ -181,6 +175,23 @@
[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 = Overlay t&c
......@@ -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
- (void)requestAppointment {
[APIClient requestAppointment:^(BOOL success, NSDictionary *data, NSError *error) {
......@@ -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
......@@ -111,29 +111,29 @@
}
- (void)checkAndRefreshAPIs {
NSLog(@"🔍 Checking credentials...");
NSLog(@"ClientID: %@", self.ClientID);
NSLog(@"ClientCode: %@", self.ClientCode);
NSLog(@"user_token: %@", self.user_token);
[self showLoadingOverlay:@"Loading..."];
if (![self isReadyToLoadDashboard]) {
NSLog(@"⏳ Waiting for all credentials to be ready...");
return;
}
if (_isLoadingDashboard) return;
self.isLoadingDashboard = YES;
NSLog(@"✅ All credentials ready. ClientID: %@, ClientCode: %@, user_token: %@",
self.ClientID, self.ClientCode, self.user_token);
[APIConfig setAuthTokem:self.user_token];
[APIConfig setClientId:self.ClientID];
// self.ClientCode = @"spt";
// NSLog(@"🔍 Checking credentials...");
// NSLog(@"ClientID: %@", self.ClientID);
// NSLog(@"ClientCode: %@", self.ClientCode);
// NSLog(@"user_token: %@", self.user_token);
//
// [self showLoadingOverlay:@"Loading..."];
//
// if (![self isReadyToLoadDashboard]) {
// NSLog(@"⏳ Waiting for all credentials to be ready...");
// return;
// }
//
// if (_isLoadingDashboard) return;
//
// self.isLoadingDashboard = YES;
//
// NSLog(@"✅ All credentials ready. ClientID: %@, ClientCode: %@, user_token: %@",
// self.ClientID, self.ClientCode, self.user_token);
//
//
// [APIConfig setAuthTokem:self.user_token];
// [APIConfig setClientId:self.ClientID];
self.ClientCode = @"spt";
[self requestCompanyCode];
}
......
......@@ -3,7 +3,7 @@
#import "DetailsViewController.h"
#import "APIClient.h"
#import <AVFoundation/AVFoundation.h>
#import "IssueTutorialViewController.h"
#import "IssuesTutorialViewController.h"
#import "ImageCacheHelper.h"
@interface PlanViewController ()
......@@ -375,6 +375,7 @@
forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.helpButton];
// 🧩 Title label (project name) — now outside the header
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
......
// 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
// IssueTutorialViewController.mm
#import "IssuesTutorialViewController.h"
#import "TutorialBubbleView.h"
#import "TutorialStep.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;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIImageView *tutorialImageView;
@property (nonatomic, strong) NSArray<UIImage *> *tutorialImages;
@property (nonatomic, strong) NSArray<TutorialStep *> *steps;
@property (nonatomic, strong) TutorialBubbleView *bubbleView;
@property (nonatomic, assign) BOOL didLayoutInitialPage;
@end
@implementation IssuesTutorialViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
self.didLayoutInitialPage = NO;
[self setupHeader];
[self setupContent];
[self setupTutorialData];
[self setupBubble];
[self.view bringSubviewToFront:self.headerView];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (!self.didLayoutInitialPage) {
self.didLayoutInitialPage = YES;
// showPage now that layout is ready
[self showPage:self.currentPage];
} else {
// update bubble position for rotation / layout changes
[self updateBubblePosition];
}
}
#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.layer.borderColor = UIColor.whiteColor.CGColor;
self.skipButton.layer.borderWidth = 0.5;
self.skipButton.layer.cornerRadius = 14;
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:44],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[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 - Content
- (void)setupContent {
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.contentView];
self.tutorialImageView = [[UIImageView alloc] init];
self.tutorialImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.tutorialImageView.contentMode = UIViewContentModeScaleAspectFill;
self.tutorialImageView.clipsToBounds = YES;
[self.contentView addSubview:self.tutorialImageView];
[NSLayoutConstraint activateConstraints:@[
[self.contentView.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
[self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.tutorialImageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.tutorialImageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.tutorialImageView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.tutorialImageView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor],
]];
}
#pragma mark - Tutorial Data
- (void)setupTutorialData {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UIImage *img1 = [UIImage imageNamed:@"img_tutorial_ai01_3x"
inBundle:bundle
compatibleWithTraitCollection:nil];
UIImage *img2 = [UIImage imageNamed:@"img_tutorial_ai02_3x"
inBundle:bundle
compatibleWithTraitCollection:nil];
UIImage *img3 = [UIImage imageNamed:@"img_tutorial_ai03_3x"
inBundle:bundle
compatibleWithTraitCollection:nil];
self.tutorialImages = @[img1, img2, img3];
self.steps = @[
({
TutorialStep *s = [TutorialStep new];
s.text = @"Select an area in layout plan to add issue.";
s.anchorPoint = CGPointMake(0.5, 0.49);
s.bubbleAbove = NO;
s;
}),
({
TutorialStep *s = [TutorialStep new];
s.text = @"Move pin marker within location selected.";
s.anchorPoint = CGPointMake(0.5, 0.5);
s.bubbleAbove = NO;
s;
}),
({
TutorialStep *s = [TutorialStep new];
s.text = @"To change location, you can select the \"Reselect\" button.";
s.anchorPoint = CGPointMake(0.4, 0.86);
s.bubbleAbove = YES;
s;
})
];
self.currentPage = 0;
}
#pragma mark - Bubble
- (void)setupBubble {
self.bubbleView = [[TutorialBubbleView alloc] init];
self.bubbleView.translatesAutoresizingMaskIntoConstraints = NO;
[self.bubbleView.prevButton addTarget:self
action:@selector(backButtonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.bubbleView.nextButton addTarget:self
action:@selector(nextButtonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.bubbleView];
// Fixed width; height will auto-size
[self.bubbleView.widthAnchor constraintEqualToConstant:260].active = YES;
}
#pragma mark - Helper
- (void)onBack {
NSLog(@"on back triggered");
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)onSkip {
[self completeTutorial];
}
-(void)completeTutorial {
[self onBack];
}
-(void)updateHeaderButtons {
self.skipButton.hidden = (self.currentPage == self.steps.count - 1);
}
- (void)showPage:(NSInteger)page {
if (page < 0 || page >= self.tutorialImages.count) return;
self.tutorialImageView.image = self.tutorialImages[page];
[self updateHeaderButtons];
[self updateBubbleContent];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateBubblePosition];
});
}
- (void)nextButtonTapped {
if (self.currentPage < self.steps.count - 1) {
self.currentPage++;
[self showPage:self.currentPage];
} else {
[self completeTutorial];
}
}
- (void)backButtonTapped {
if (self.currentPage > 0) {
self.currentPage--;
[self showPage:self.currentPage];
}
}
- (CGRect)imageFrameInImageView:(UIImageView *)imageView {
if (!imageView.image) return CGRectZero;
CGSize imageSize = imageView.image.size;
CGSize viewSize = imageView.bounds.size;
CGFloat imageRatio = imageSize.width / imageSize.height;
CGFloat viewRatio = viewSize.width / viewSize.height;
CGSize drawSize;
if (imageRatio > viewRatio) {
// Image is wider → height fills, width overflows
drawSize.height = viewSize.height;
drawSize.width = viewSize.height * imageRatio;
} else {
// Image is taller → width fills, height overflows
drawSize.width = viewSize.width;
drawSize.height = viewSize.width / imageRatio;
}
CGFloat x = (viewSize.width - drawSize.width) / 2.0;
CGFloat y = (viewSize.height - drawSize.height) / 2.0;
return CGRectMake(x, y, drawSize.width, drawSize.height);
}
- (CGPoint)screenPointForCurrentStep {
TutorialStep *step = self.steps[self.currentPage];
CGRect imageFrame = [self imageFrameInImageView:self.tutorialImageView];
CGFloat x = imageFrame.origin.x + step.anchorPoint.x * imageFrame.size.width;
CGFloat y = imageFrame.origin.y + step.anchorPoint.y * imageFrame.size.height;
return CGPointMake(x, y);
}
- (void)updateBubblePosition {
if (!self.bubbleView || !self.tutorialImageView.image) return;
// Make sure bubble layout is up-to-date
[self.bubbleView layoutIfNeeded];
TutorialStep *step = self.steps[self.currentPage];
CGPoint anchor = [self screenPointForCurrentStep];
self.bubbleView.arrowDirection =
step.bubbleAbove ? BubbleArrowDirectionDown : BubbleArrowDirectionUp;
CGFloat bubbleHeight =
[self.bubbleView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGFloat bubbleWidth = 260;
CGFloat x = anchor.x - bubbleWidth / 2.0;
CGFloat y = step.bubbleAbove
? anchor.y - bubbleHeight - 12
: anchor.y + 12;
self.bubbleView.frame = CGRectMake(x, y, bubbleWidth, bubbleHeight);
}
- (void)updateBubbleContent {
TutorialStep *step = self.steps[self.currentPage];
[self.bubbleView configureWithText:step.text
step:self.currentPage
total:self.steps.count
isFirst:self.currentPage == 0
isLast:self.currentPage == self.steps.count - 1];
self.bubbleView.prevButton.hidden = (self.currentPage == 0);
}
@end
......@@ -415,7 +415,4 @@ heightForHeaderInSection:(NSInteger)section {
self.view.userInteractionEnabled = !isLoading;
}
@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