@@ -13,6 +13,7 @@ import MastodonAsset
1313import MastodonCore
1414import MastodonLocalization
1515import MastodonUI
16+ import SDWebImage
1617
1718class MainTabBarController : UITabBarController {
1819
@@ -218,11 +219,15 @@ extension MainTabBarController {
218219 . receive ( on: DispatchQueue . main)
219220 . sink { [ weak self] avatarURL in
220221 guard let self else { return }
221- self . avatarButton. avatarImageView. setImage (
222- url: avatarURL,
223- placeholder: . placeholder( color: . systemFill) ,
224- scaleToSize: MainTabBarController . avatarButtonSize
225- )
222+ if #available( iOS 26 , * ) {
223+ self . layoutAvatarButton ( )
224+ } else {
225+ self . avatarButton. avatarImageView. setImage (
226+ url: avatarURL,
227+ placeholder: . placeholder( color: . systemFill) ,
228+ scaleToSize: MainTabBarController . avatarButtonSize
229+ )
230+ }
226231 }
227232 . store ( in: & disposeBag)
228233
@@ -379,39 +384,50 @@ extension MainTabBarController {
379384 }
380385
381386 private func layoutAvatarButton( ) {
382- guard avatarButton. superview == nil else { return }
383-
384- guard let profileTabItem = meProfileViewController. tabBarItem else { return }
385- guard let view = profileTabItem. value ( forKey: " view " ) as? UIView else {
386- return
387- }
388-
389- let _anchorImageView = view. subviews. first { subview in subview is UIImageView } as? UIImageView
390- guard let anchorImageView = _anchorImageView else {
391- assertionFailure ( )
392- return
387+ if #available( iOS 26 , * ) {
388+ guard let avatarURL else { return }
389+ Task { [ weak self] in
390+ guard let self else { return }
391+ let avatarImage = try await AvatarButtonImageLoader . getImage ( url: avatarURL)
392+ guard self . avatarURL == avatarURL else { return }
393+ let tabBarItem = UITabBarItem ( title: nil , image: avatarImage? . withRenderingMode ( . alwaysOriginal) , tag: Tab . me. tag)
394+ self . meProfileViewController. tabBarItem = tabBarItem
395+ }
396+ } else {
397+ guard avatarButton. superview == nil else { return }
398+
399+ guard let profileTabItem = meProfileViewController. tabBarItem else { return }
400+ guard let view = profileTabItem. value ( forKey: " view " ) as? UIView else {
401+ return
402+ }
403+
404+ let _anchorImageView = view. subviews. first { subview in subview is UIImageView } as? UIImageView
405+ guard let anchorImageView = _anchorImageView else {
406+ assertionFailure ( )
407+ return
408+ }
409+ anchorImageView. alpha = 0
410+
411+ accountSwitcherChevron. removeFromSuperview ( )
412+ accountSwitcherChevron. translatesAutoresizingMaskIntoConstraints = false
413+ view. addSubview ( accountSwitcherChevron)
414+
415+ self . avatarButton. translatesAutoresizingMaskIntoConstraints = false
416+ view. addSubview ( self . avatarButton)
417+ NSLayoutConstraint . activate ( [
418+ self . avatarButton. centerXAnchor. constraint ( equalTo: anchorImageView. centerXAnchor) ,
419+ self . avatarButton. centerYAnchor. constraint ( equalTo: anchorImageView. centerYAnchor) ,
420+ self . avatarButton. widthAnchor. constraint ( equalToConstant: MainTabBarController . avatarButtonSize. width) . priority ( . required - 1 ) ,
421+ self . avatarButton. heightAnchor. constraint ( equalToConstant: MainTabBarController . avatarButtonSize. height) . priority ( . required - 1 ) ,
422+ accountSwitcherChevron. widthAnchor. constraint ( equalToConstant: 10 ) ,
423+ accountSwitcherChevron. heightAnchor. constraint ( equalToConstant: 18 ) ,
424+ accountSwitcherChevron. leadingAnchor. constraint ( equalTo: avatarButton. trailingAnchor, constant: 8 ) ,
425+ accountSwitcherChevron. centerYAnchor. constraint ( equalTo: avatarButton. centerYAnchor)
426+ ] )
427+ self . avatarButton. setContentHuggingPriority ( . required - 1 , for: . horizontal)
428+ self . avatarButton. setContentHuggingPriority ( . required - 1 , for: . vertical)
429+ self . avatarButton. isUserInteractionEnabled = false
393430 }
394- anchorImageView. alpha = 0
395-
396- accountSwitcherChevron. removeFromSuperview ( )
397- accountSwitcherChevron. translatesAutoresizingMaskIntoConstraints = false
398- view. addSubview ( accountSwitcherChevron)
399-
400- self . avatarButton. translatesAutoresizingMaskIntoConstraints = false
401- view. addSubview ( self . avatarButton)
402- NSLayoutConstraint . activate ( [
403- self . avatarButton. centerXAnchor. constraint ( equalTo: anchorImageView. centerXAnchor) ,
404- self . avatarButton. centerYAnchor. constraint ( equalTo: anchorImageView. centerYAnchor) ,
405- self . avatarButton. widthAnchor. constraint ( equalToConstant: MainTabBarController . avatarButtonSize. width) . priority ( . required - 1 ) ,
406- self . avatarButton. heightAnchor. constraint ( equalToConstant: MainTabBarController . avatarButtonSize. height) . priority ( . required - 1 ) ,
407- accountSwitcherChevron. widthAnchor. constraint ( equalToConstant: 10 ) ,
408- accountSwitcherChevron. heightAnchor. constraint ( equalToConstant: 18 ) ,
409- accountSwitcherChevron. leadingAnchor. constraint ( equalTo: avatarButton. trailingAnchor, constant: 8 ) ,
410- accountSwitcherChevron. centerYAnchor. constraint ( equalTo: avatarButton. centerYAnchor)
411- ] )
412- self . avatarButton. setContentHuggingPriority ( . required - 1 , for: . horizontal)
413- self . avatarButton. setContentHuggingPriority ( . required - 1 , for: . vertical)
414- self . avatarButton. isUserInteractionEnabled = false
415431 }
416432
417433 private func updateAvatarButtonAppearance( ) {
@@ -690,3 +706,58 @@ extension MainTabBarController: UINavigationControllerDelegate {
690706 }
691707 }
692708}
709+
710+ struct AvatarButtonImageLoader {
711+ static var _cache = [ URL : UIImage] ( )
712+
713+ static func getImage( url: URL ) async throws -> UIImage ? {
714+ if let cached = _cache [ url] {
715+ return cached
716+ } else {
717+ let baseImage : UIImage ? = try await withCheckedThrowingContinuation { continuation in
718+ SDWebImageDownloader . shared. downloadImage ( with: url) { image, data, error, finished in
719+ if let error {
720+ continuation. resume ( throwing: error)
721+ } else {
722+ continuation. resume ( returning: image)
723+ }
724+ }
725+ }
726+
727+ guard let circular = baseImage? . circularMasked ( diameter: 40 ) else { return nil }
728+ _cache [ url] = circular
729+ return circular
730+ }
731+ }
732+
733+ }
734+
735+ var circularAvatarRenderer : UIGraphicsImageRenderer ?
736+
737+ extension UIImage {
738+ func circularMasked( diameter: CGFloat ) -> UIImage ? {
739+ guard let cgImage = self . cgImage else { return nil }
740+ let scale = max ( diameter/ size. width, diameter/ size. height)
741+ let drawingRect = CGRect (
742+ x: ( size. width * scale - diameter) / 2 ,
743+ y: ( size. height * scale - diameter) / 2 ,
744+ width: size. width * scale,
745+ height: size. height * scale
746+ )
747+
748+ let renderer = {
749+ if let circularAvatarRenderer {
750+ return circularAvatarRenderer
751+ } else {
752+ let _newRenderer = UIGraphicsImageRenderer ( size: CGSize ( width: diameter, height: diameter) )
753+ circularAvatarRenderer = _newRenderer
754+ return _newRenderer
755+ }
756+ } ( )
757+ return renderer. image { context in
758+ let circleRect = CGRect ( origin: . zero, size: CGSize ( width: diameter, height: diameter) )
759+ UIBezierPath ( ovalIn: circleRect) . addClip ( )
760+ UIImage ( cgImage: cgImage) . draw ( in: drawingRect)
761+ }
762+ }
763+ }
0 commit comments