Skip to content

Commit d2eae0b

Browse files
Added YTHidePlayerButtons 1.0.0
Replaces my old code with a completely new YouTube Tweak that does its job. Some code was based off of YouPiP, Credits to PoomSmart & NguyenASang
1 parent 7c46e2d commit d2eae0b

File tree

1 file changed

+147
-116
lines changed

1 file changed

+147
-116
lines changed

Sources/uYouPlus.xm

Lines changed: 147 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -115,145 +115,176 @@ static int getNotificationIconStyle() {
115115
%end
116116
%end
117117

118-
/* LEGACY VERSION ⚠️
119-
This method uses UIView accessibilityLabel to hide classic video player buttons.
120-
It's less reliable for new YouTube UI and only works for connect/thanks/save/report.
121-
Now fully commented out for the NEW VERSION.
122-
*/
123-
/*
124-
%hook _ASDisplayView
125-
- (void)layoutSubviews {
126-
%orig;
127-
BOOL hideConnectButton = IS_ENABLED(@"hideConnectButton_enabled");
128-
BOOL hideThanksButton = IS_ENABLED(@"hideThanksButton_enabled");
129-
BOOL hideSaveToPlaylistButton = IS_ENABLED(@"hideSaveToPlaylistButton_enabled");
130-
BOOL hideReportButton = IS_ENABLED(@"hideReportButton_enabled");
131-
132-
for (UIView *subview in self.subviews) {
133-
if ([subview.accessibilityLabel isEqualToString:@"connect account"]) {
134-
subview.hidden = hideConnectButton;
135-
} else if ([subview.accessibilityLabel isEqualToString:@"Thanks"]) {
136-
subview.hidden = hideThanksButton;
137-
} else if ([subview.accessibilityLabel isEqualToString:@"Save to playlist"]) {
138-
subview.hidden = hideSaveToPlaylistButton;
139-
} else if ([subview.accessibilityLabel isEqualToString:@"Report"]) {
140-
subview.hidden = hideReportButton;
141-
}
142-
}
118+
// YTHidePlayerButtons 1.0.0 - made by @aricloverEXTRA
119+
static NSDictionary<NSString *, NSString *> *HideToggleMap(void) {
120+
static NSDictionary<NSString *, NSString *> *map = nil;
121+
static dispatch_once_t onceToken;
122+
dispatch_once(&onceToken, ^{
123+
map = @{
124+
// identifiers
125+
@"id.video.share.button": @"hideShareButton_enabled",
126+
@"id.video.remix.button": @"hideRemixButton_enabled",
127+
@"clip_button.eml": @"hideClipButton_enabled",
128+
@"id.ui.add_to.offline.button": @"hideDownloadButton_enabled",
129+
@"id.ui.carousel_header": @"hideCommentSection_enabled",
130+
// fallbacks
131+
@"Share": @"hideShareButton_enabled",
132+
@"Remix": @"hideRemixButton_enabled",
133+
@"Clip": @"hideClipButton_enabled",
134+
@"Download": @"hideDownloadButton_enabled",
135+
@"Save to playlist": @"hideSaveToPlaylistButton_enabled",
136+
@"Report": @"hideReportButton_enabled",
137+
@"Thanks": @"hideThanksButton_enabled",
138+
@"connect account": @"hideConnectButton_enabled",
139+
@"Like": @"hideLikeButton_enabled",
140+
@"Dislike": @"hideDislikeButton_enabled"
141+
};
142+
});
143+
return map;
143144
}
144-
%end
145-
*/
146-
147-
// --- NEW VERSION ---
148-
// This method traverses ASNodeController/ELMNodeController to find and hide modern action bar buttons.
149-
// It is more robust for YouTube v19+ and node-based UI.
150-
// Now with crash prevention and robust checks.
151-
152-
static BOOL shouldHideButton(NSString *buttonKey) {
153-
if ([buttonKey isEqualToString:@"id.video.share.button"]) return IS_ENABLED(@"hideShareButton_enabled");
154-
if ([buttonKey isEqualToString:@"id.video.remix.button"]) return IS_ENABLED(@"hideRemixButton_enabled");
155-
if ([buttonKey isEqualToString:@"Thanks"]) return IS_ENABLED(@"hideThanksButton_enabled");
156-
if ([buttonKey isEqualToString:@"clip_button.eml"]) return IS_ENABLED(@"hideClipButton_enabled");
157-
if ([buttonKey isEqualToString:@"id.ui.add_to.offline.button"]) return IS_ENABLED(@"hideDownloadButton_enabled");
158-
if ([buttonKey isEqualToString:@"id.ui.carousel_header"]) return IS_ENABLED(@"hideCommentSection_enabled");
159-
// Add more as needed
160-
return NO;
145+
static BOOL shouldHideForKey(NSString *key) {
146+
if (!key) return NO;
147+
NSString *pref = HideToggleMap()[key];
148+
if (!pref) return NO;
149+
return IS_ENABLED(pref);
161150
}
162-
163-
static void safeSetHidden(id node, BOOL hidden) {
164-
if (!node) return;
151+
static void safeHideView(id view) {
152+
if (!view) return;
153+
dispatch_async(dispatch_get_main_queue(), ^{
154+
@try {
155+
if ([view respondsToSelector:@selector(setHidden:)]) {
156+
[view setHidden:YES];
157+
return;
158+
}
159+
if ([view isKindOfClass:[UIView class]]) {
160+
((UIView *)view).hidden = YES;
161+
return;
162+
}
163+
} @catch (NSException *ex) {
164+
NSLog(@"[HidePlayerButtons] safeHideView exception: %@", ex);
165+
}
166+
});
167+
}
168+
static BOOL inspectAndHideIfMatch(id view) {
169+
if (!view) return NO;
165170
@try {
166-
if ([node respondsToSelector:@selector(setHidden:)]) {
167-
[node setHidden:hidden];
168-
} else if ([node isKindOfClass:[ASDisplayNode class]]) {
169-
((ASDisplayNode *)node).hidden = hidden;
171+
NSString *accId = nil;
172+
if ([view respondsToSelector:@selector(accessibilityIdentifier)]) {
173+
@try { accId = [view accessibilityIdentifier]; } @catch (NSException *e) { accId = nil; }
174+
if (accId && shouldHideForKey(accId)) {
175+
safeHideView(view);
176+
return YES;
177+
}
170178
}
171-
} @catch (NSException *exception) {
172-
NSLog(@"[HidePlayerButtons] Exception hiding node: %@", exception);
179+
NSString *accLabel = nil;
180+
if ([view respondsToSelector:@selector(accessibilityLabel)]) {
181+
@try { accLabel = [view accessibilityLabel]; } @catch (NSException *e) { accLabel = nil; }
182+
if (accLabel && shouldHideForKey(accLabel)) {
183+
safeHideView(view);
184+
return YES;
185+
}
186+
}
187+
NSString *desc = nil;
188+
@try { desc = [[view description] copy]; } @catch (NSException *e) { desc = nil; }
189+
if (desc) {
190+
for (NSString *key in HideToggleMap().allKeys) {
191+
if ([desc containsString:key] && shouldHideForKey(key)) {
192+
safeHideView(view);
193+
return YES;
194+
}
195+
}
196+
}
197+
} @catch (NSException *ex) {
198+
NSLog(@"[HidePlayerButtons] inspectAndHideIfMatch exception: %@", ex);
173199
}
200+
return NO;
174201
}
175-
176-
static BOOL findAndHideCell(ASNodeController *nodeController, NSArray<NSString *> *identifiers) {
177-
if (!nodeController || ![nodeController respondsToSelector:@selector(children)]) return NO;
202+
static void traverseAndHideViews(UIView *root) {
203+
if (!root) return;
178204
@try {
179-
for (id child in [nodeController children]) {
180-
// ELMNodeController
181-
if ([child isKindOfClass:%c(ELMNodeController)]) {
182-
NSArray <ELMComponent *> *elmChildren = [(ELMNodeController*)child children];
183-
for (ELMComponent *elmChild in elmChildren) {
184-
for (NSString *identifier in identifiers) {
185-
if ([[elmChild description] containsString:identifier]) {
186-
if (shouldHideButton(identifier)) {
187-
safeSetHidden(elmChild, YES);
188-
return YES;
189-
}
190-
}
191-
}
205+
inspectAndHideIfMatch(root);
206+
NSArray<UIView *> *subs = nil;
207+
@try { subs = root.subviews; } @catch (NSException *e) { subs = nil; }
208+
if (subs && subs.count) {
209+
for (UIView *sv in subs) {
210+
if ([sv isKindOfClass:[UIView class]]) {
211+
traverseAndHideViews(sv);
192212
}
193213
}
194-
// ASNodeController
195-
if ([child isKindOfClass:%c(ASNodeController)]) {
196-
ASDisplayNode *childNode = nil;
214+
}
215+
} @catch (NSException *ex) {
216+
NSLog(@"[HidePlayerButtons] traverseAndHideViews exception: %@", ex);
217+
}
218+
}
219+
static void hideButtonsInActionBarIfNeeded(id collectionView) {
220+
if (!collectionView) return;
221+
@try {
222+
// Ensure the collectionView has accessibilityIdentifier and we only operate on the action bar
223+
NSString *accId = nil;
224+
if ([collectionView respondsToSelector:@selector(accessibilityIdentifier)]) {
225+
@try { accId = [collectionView accessibilityIdentifier]; } @catch (NSException *e) { accId = nil; }
226+
}
227+
if (!accId) return;
228+
if (![accId isEqualToString:@"id.video.scrollable_action_bar"]) return;
229+
NSArray *cells = nil;
230+
if ([collectionView respondsToSelector:@selector(visibleCells)]) {
231+
@try { cells = [collectionView visibleCells]; } @catch (NSException *e) { cells = nil; }
232+
}
233+
if (!cells || cells.count == 0) {
234+
@try { cells = [collectionView subviews]; } @catch (NSException *e) { cells = nil; }
235+
}
236+
if (!cells || cells.count == 0) return;
237+
for (id cell in cells) {
238+
if ([cell isKindOfClass:[UIView class]]) {
239+
traverseAndHideViews((UIView *)cell);
240+
} else {
197241
@try {
198-
childNode = ((ASNodeController*)child).node;
199-
} @catch (NSException *exception) {
200-
NSLog(@"[HidePlayerButtons] Exception accessing child node: %@", exception);
201-
continue;
202-
}
203-
if ([childNode respondsToSelector:@selector(yogaChildren)]) {
204-
NSArray<id> *yogaChildren = childNode.yogaChildren;
205-
for (ASDisplayNode *displayNode in yogaChildren) {
206-
if ([displayNode respondsToSelector:@selector(accessibilityIdentifier)]) {
207-
if (shouldHideButton(displayNode.accessibilityIdentifier)) {
208-
safeSetHidden(displayNode, YES);
209-
return YES;
210-
}
242+
if ([cell respondsToSelector:@selector(view)]) {
243+
id view = [cell performSelector:@selector(view)];
244+
if ([view isKindOfClass:[UIView class]]) {
245+
traverseAndHideViews((UIView *)view);
211246
}
212-
if (findAndHideCell(child, identifiers)) {
213-
return YES;
247+
} else if ([cell respondsToSelector:@selector(node)]) {
248+
NSString *desc = nil;
249+
@try { desc = [cell description]; } @catch (NSException *e) { desc = nil; }
250+
if (desc) {
251+
// Not ideal to act on description, but we keep this non-destructive: only log for debugging
252+
// Uncomment logging for debug builds if needed.
253+
// NSLog(@"[HidePlayerButtons] Non-UIView cell description: %@", desc);
214254
}
215255
}
256+
} @catch (NSException *ex) {
257+
NSLog(@"[HidePlayerButtons] Exception handling non-UIView cell: %@", ex);
216258
}
217259
}
218260
}
219261
} @catch (NSException *exception) {
220-
NSLog(@"[HidePlayerButtons] Exception traversing nodeController: %@", exception);
262+
NSLog(@"[HidePlayerButtons] hideButtonsInActionBarIfNeeded exception: %@", exception);
221263
}
222-
return NO;
223264
}
224-
225265
%hook ASCollectionView
226-
- (CGSize)sizeForElement:(ASCollectionElement * _Nullable)element {
227-
// Only target the video action bar (player buttons bar)
228-
if ([self.accessibilityIdentifier isEqualToString:@"id.video.scrollable_action_bar"]) {
229-
ASCellNode *node = nil;
230-
ASNodeController *nodeController = nil;
266+
- (id)nodeForItemAtIndexPath:(NSIndexPath *)indexPath {
267+
id node = %orig;
268+
id weakSelf = (id)self;
269+
dispatch_async(dispatch_get_main_queue(), ^{
231270
@try {
232-
node = [element node];
233-
nodeController = [node controller];
234-
} @catch (NSException *exception) {
235-
NSLog(@"[HidePlayerButtons] Exception accessing node/controller: %@", exception);
236-
return %orig;
271+
hideButtonsInActionBarIfNeeded(weakSelf);
272+
} @catch (NSException *e) {
273+
NSLog(@"[HidePlayerButtons] async hide exception: %@", e);
237274
}
238-
NSArray<NSString *> *buttonIdentifiers = @[
239-
@"id.video.share.button",
240-
@"id.video.remix.button",
241-
@"Thanks",
242-
@"clip_button.eml",
243-
@"id.ui.add_to.offline.button",
244-
@"id.ui.carousel_header"
245-
// Add/expand with other button keys as needed
246-
];
247-
// Traverse and hide
248-
findAndHideCell(nodeController, buttonIdentifiers);
249-
// Optionally, if you want to remove layout space entirely:
250-
for (NSString *identifier in buttonIdentifiers) {
251-
if (shouldHideButton(identifier) && findAndHideCell(nodeController, @[identifier])) {
252-
return CGSizeZero;
253-
}
275+
});
276+
return node;
277+
}
278+
- (void)nodesDidRelayout:(NSArray *)nodes {
279+
%orig;
280+
id weakSelf = (id)self;
281+
dispatch_async(dispatch_get_main_queue(), ^{
282+
@try {
283+
hideButtonsInActionBarIfNeeded(weakSelf);
284+
} @catch (NSException *e) {
285+
NSLog(@"[HidePlayerButtons] relayout hide exception: %@", e);
254286
}
255-
}
256-
return %orig;
287+
});
257288
}
258289
%end
259290

0 commit comments

Comments
 (0)