Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VoiceOver very laggy/slow on screen with many subViews

I am building full accessibility into my iOS Game called Swordy Quest: https://apps.apple.com/us/app/swordy-quest-an-rpg-adventure/id1446641513

As you can see from the screenshots on the above link, there is a Map I have created with 50x50 individual UIViews with a UIButton on each all located on a UIScrollView. With VoiceOver turned off the whole app (including the Map section) works fine - though the map can be a little slow to load at times. When I turn on VoiceOver the whole app responds fine except for the Map Section, which gets very laggy - almost unplayable on my iPhone 7 (like to have an old phone to test worst user experiences).

I have tried removing image detail if VoiceOver is turned on, but that makes no difference at all. This is making me think the lag is due to the 50 x 50 UIViews all of which have an accessibilityLabel added. Does VoiceOver start to lag badly if there are too many accessible labels on a single UIViewController?

Does anyone know a clever way to get around this? I wondered if maybe was a clever way you could turn off AccessibilityLabels except for when a UIView/UIButton is in the visible section of the UIScrollView?

like image 873
Charlie Seligman Avatar asked Jun 21 '21 21:06

Charlie Seligman


People also ask

How to solve OBS laggy recording issue?

Method 1: Solve OBS laggy recording by optimizing your computer If your CPU and GPU are overloaded, we can simply shut down some unnecessary processes.

How to fix lag between two monitors in Windows 10?

If the color depth on two monitors is set to be different, the lag issue can occur. To fix the problem, you can try to set two monitors to the same color depth. To do so: 1) Right-click on the desktop and select Display settings to open the Display settings window. 2) Scroll down a little bit then click the Advanced display settings link.

How do I get rid of lag on my TV screen?

Plug your phone directly to the TV to get rid of the lag. With wireless connection, especially if you don’t an HDMI adapter, there is a great chance for delays. Screen mirroring is a powerful feature you can use to display what’s on your smartphone to a larger screen.

How to make OBS record smoother?

You can check out the following methods to make OBS record smoother: Method 1: Solve OBS laggy recording by optimizing your computer. Method 2: Solve OBS laggy recording by adjusting OBS settings.


Video Answer


1 Answers

You should not instantiate and render 2500 views at once.
Modern day devices may handle it reasonably well but it still impacts performance and memory usage and should be avoided.
Supporting VoiceOver just surfaces this kind of bad practice.

The right tool to use here is a UICollectionView. Only visible views will be loaded to memory, which limits the performance impact significantly. You can easily implement custom layouts of any kind including a x/y scrollable tile-map like you need it.

Please see the following minimal implementation to get you started. You can copy the code to the AppDelegate.swift file of a freshly created Xcode project to play around with and adapt it to your needs.

// 1. create new Xcode project
// 2. delete all swift files except AppDelegate
// 3. delete storyboard
// 4. delete references to storyboard and scene from info.plist
// 5. copy the following code to AppDelegate

import UIKit


// boilerplate
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        let layout = MapLayout(columnCount: 50, itemSize: 50)!
        self.window?.rootViewController = ViewController(collectionViewLayout: layout)
        self.window?.makeKeyAndVisible()
        return true
    }
}

class MapLayout: UICollectionViewLayout {
    
    let columnCount: Int
    let itemSize: CGFloat
    var layoutAttributesCache = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()

    init?(columnCount: Int, itemSize: CGFloat) {
        guard columnCount > 0 else { return nil }
        self.columnCount = columnCount
        self.itemSize = itemSize
        super.init()
    }

    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    override var collectionViewContentSize: CGSize {
        let itemCount = self.collectionView?.numberOfItems(inSection: 0) ?? 0
        let width = CGFloat(min(itemCount, self.columnCount)) * itemSize
        let height = ceil(CGFloat(itemCount / self.columnCount)) * itemSize
        return CGSize(width: width, height: height)
    }

    // the interesting part: here the layout is calculated
    override func prepare() {
        let itemCount = self.collectionView?.numberOfItems(inSection: 0) ?? 0
        for index in 0..<itemCount {

            let xIndex = index % self.columnCount
            let yIndex = Int( Double(index / self.columnCount) )

            let xPos = CGFloat(xIndex) * self.itemSize
            let yPos = CGFloat(yIndex) * self.itemSize

            let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: index, section: 0))
            cellAttributes.frame = CGRect(x: xPos, y: yPos, width: self.itemSize, height: self.itemSize)

            self.layoutAttributesCache[cellAttributes.indexPath] = cellAttributes
        }
    }
        
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        layoutAttributesCache.values.filter { rect.intersects($0.frame) }
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        self.layoutAttributesCache[indexPath]!
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { false }
}

// boilerplate
class ViewController: UICollectionViewController {

    var columnCount: Int { (self.collectionViewLayout as! MapLayout).columnCount }
    var rowCount: Int { columnCount }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int { 1 }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { rowCount * columnCount }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        let fancyColor = UIColor(hue: CGFloat((indexPath.row % columnCount))/CGFloat(columnCount), saturation: 1, brightness: 1 - floor( Double(indexPath.row) / Double( columnCount) ) / Double(rowCount), alpha: 1).cgColor
        cell.layer.borderColor = fancyColor
        cell.layer.borderWidth = 2
        return cell
    }
}

The result should look something like this:

tilemap collection view demo

like image 80
de. Avatar answered Oct 22 '22 23:10

de.