#StackBounty: #ios #swift #uitableview #uiwebview How to optimize loading large and varied HTML content in UIWebView inside multiple Ta…

Bounty: 50

I have a UITableViewCell that has a UIWebView inside it.
Data I load from the server has several comments containing rich HTML content(text, images, links, etc.) As the cell contains UIWebView, I have to wait till the entire content is loaded to get the height of the tableViewCell and update it.

The issue I’m running into is that as I scroll down the cells get updated and the whole view jerks and flickers. Basically, I’m looking for a way to preload the webviews to get their height beforehand. Also, this jerking only happens until all the cells have loaded the content since I’m storing the height of each cell locally.

Here’s an example of the jittery experience.

Code of cellForRowIndex

open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(indexPath: indexPath) as LiMessageDetailTableViewCell
    cell.delegate = self
    cell.cellModel = LiMessageDetailTableViewCellModel(data: messageObject.originalMessage, index: indexPath)
    cell.heightOfWebView = messageObject.contentHeightsOriginalMessage[indexPath.row]
    if messageObject.contentHeightsOriginalMessage[indexPath.row] != 0.0 {
        cell.updateHeightConstrain()
    }
    return cell
}

WebView delegate method in TableViewCell

 //MARK:- WEBVIEW DELEGATE
    func webViewDidFinishLoad(_ webView: UIWebView) {
     //This section resizes the webview according to the new content loaded.
        var mframe = webView.frame
        mframe.size.height = 1
        webView.frame = mframe
        let fittingSize = webView.sizeThatFits(.zero)
        mframe.size = fittingSize
        webView.frame = mframe
    /**
    I found that javascript gives a more accurate height when images are
    included since they take more time loading than the normal content and 
    hence sizeThatFits does not always return proper result.
    **/
        let heightInString = webView.stringByEvaluatingJavaScript(from: "document.body.scrollHeight") ?? ""
        guard let heightInFloat = Float(heightInString) else { return }
        guard let index = cellModel?.indexPath else {return}
        constrainHeightOfWebView.constant = fittingSize.height
        guard let cellType = cellModel?.messageType  else { return }
        delegate?.updateHeight(index: index, newHeight: CGFloat(heightInFloat), cellType: cellType)
    }

Delgate method which updates the height

func updateHeight(index: IndexPath, newHeight: CGFloat, cellType: LiMessageType) {
    switch cellType {
    case .originalMessage:
        if msgObj.contentHeightsOriginalMessage[index.row] != 0.0 && msgObj.contentHeightsOriginalMessage[index.row] == newHeight {
            return
        }
        msgObj.contentHeightsOriginalMessage[index.row] = newHeight
   .....
    }
    self.tableView.beginUpdates()
    self.tableView.endUpdates()
}

Another issue is that if the webview contains heavy images then the whole webview flickers as it reloads everytime the cell is dequeued, causing a bad experience.

Here’s the example of webview with image reloading

The image issue resolves itself after some time as webview gets cached, but nevertheless a bad experience.

Is there a way to solve these issues?


Get this bounty!!!

#StackBounty: #ios #swift #uitextfield Cursor goes to end after setting text to UITextField inside shouldChangeCharactersIn

Bounty: 50

I have text label which has phone number in it. I mask the phone number when user is typing so that in shouldChangeCharactersIn function;

  • I get user input (string)
  • Add that input to text which is already written in UITextField
  • Mask text and set it to UITextField
  • Return false

My question is that after I set text of UITextfield (delete or add new character UITextField, cursor moves to the end but I want it to stay in the same position. (By meaning same position, I mean same when I don’t implement shouldChangeCharactersIn function) How can I do that? Thank you.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    guard CharacterSet(charactersIn: "0123456789").isSuperset(of: CharacterSet(charactersIn: string)) else {
        return false
    }
    if let text = textField.text {
        let newLength = text.count + string.count - range.length
        let newText = text + string

        let textFieldText: NSString = (textField.text ?? "") as NSString
        let txtAfterUpdate = textFieldText.replacingCharacters(in: range, with: string)
            if(newLength <= 15){
                //textField.text = txtAfterUpdate
                textField.text = txtAfterUpdate.digits.applyPatternOnNumbers()
                return false
            }
            return newLength <= 15
    }
    return true
}

Mask Function:

func applyPatternOnNumbers(pattern: String = "(###) ### ## ##", replacmentCharacter: Character = "#") -> String {
var pureNumber = self.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression)
for index in 0 ..< pattern.count {
    guard index < pureNumber.count else { return pureNumber }
    let stringIndex = String.Index(encodedOffset: index)
    let patternCharacter = pattern[stringIndex]
    guard patternCharacter != replacmentCharacter else { continue }
    pureNumber.insert(patternCharacter, at: stringIndex)
}
return pureNumber
}

What I want in GIF

enter image description here


Get this bounty!!!

#StackBounty: #swift #ios #cocoa-touch Presenting and passing data to a modal view controller without using prepare(for:sender:) method

Bounty: 50

I am using a toolbar button to present a modal view controller (in which I let the user export data as a PDF file). The main section of my app is a UITableViewController subclass embedded in a UINavigationController.

Here is a schematic of my layout.

Schematic of my storyboard layout

The modal itself is embedded in a UINavigationController as I need it to have a bottom toolbar. It also has a transparent background and is presented using .overCurrentContext, so the main screen of the user’s data blurs underneath.

I found that to get it to float over everything else (including the navigation bar etc), I had to present it from the UINavigationController (otherwise the main navigation bar and toolbar appeared on top of it).
The problem with this is that the UITableViewController method prepare(for:sender:) is not called.

I call the segue to the modal view controller like this (from the UITableViewController subclass):

// User taps EXPORT button
@objc func exportButtonTapped(_ sender: UIBarButtonItem) {
    self.navigationController?.performSegue(withIdentifier: "showExport", sender: nil)
}

In order to transfer the array of user data to the modal view controller, I have called the following code in the modal view controller:

override func viewDidLoad() {
    super.viewDidLoad()
    // Get data from array in main table view controller
    let masterNav = navigationController?.presentingViewController as! UINavigationController
    let myTableVC = masterNav.topViewController as! MyTableViewController
    self.userData = myTableVC.userData // This is of type: [MyObject]
} 

The data is then rendered to a PDF (using HTML templating) in the modal view controller’s viewWillAppear() method. This works as expected.

However, I have some concerns about doing it this way:

  1. Is it guaranteed that viewDidLoad() will finish before viewWillAppear() is called? Will an even a larger data set be available for rendering as PDF in viewWillAppear()?
  2. Is it acceptable to present modally from the UINavigationController?
  3. Should I be subclassing the main UINavigationController and using its prepare(for:sender:) method (if this is even an option)?
  4. In the performSegue(withIdentifier:sender:) method, does the sender argument make any difference?
  5. Is it preferable to use present() rather than a segue?

I would of course be grateful for any other advice or refinements to the code. It seems to work as expected; I just want to make sure I won’t run into problems in the future. Thank you.


Get this bounty!!!

#StackBounty: #ios #swift PDFKit Swapping Content of Annotation

Bounty: 150

Can the text of a FreeText annotation be changed in PDFKit without deleting an annotation / building a new annotation?

The following snippet does not change an annotation’s contents when viewing in a PDFView:

let url = Bundle.main.url(forResource: "Test", withExtension: "pdf")!
let document = PDFDocument(url: url)!

for index in 0..<document.pageCount {
    let page: PDFPage = document.page(at: index)!
    let annotations = page.annotations
    for annotation in annotations {
        annotation.contents = "[REPLACED]"
    }
}

This works – but requires replacing an annotation (and thus having to copy over all the other details of the annotation):

let url = Bundle.main.url(forResource: "Test", withExtension: "pdf")!
let document = PDFDocument(url: url)!

for index in 0..<document.pageCount {
    let page: PDFPage = document.page(at: index)!
    let annotations = page.annotations
    for annotation in annotations {
        print(annotation)
        page.removeAnnotation(annotation)
        let replacement = PDFAnnotation(bounds: annotation.bounds,
                                        forType: .freeText,
                                        withProperties: nil)

        replacement.contents = "[REPLACED]"
        page.addAnnotation(replacement)
    }
}


Get this bounty!!!

#StackBounty: #ios #swift swift: How to create UIPageViewController?

Bounty: 50

I want to create UIPageViewController with spine mid location. I read several tutorials with UIPageViewController but in this tutorials used spine min or max location. And I can not create UIPageViewController with spine mid location.

I have this function to create UIPageViewController with spine mid location min or max location:

func createPageViewController() {

            // Instantiate the PageViewController
            let pageController = self.storyboard?.instantiateViewController(withIdentifier: "PageViewController") as! UIPageViewController
            pageController.dataSource = self
            pageController.delegate = self

            if images.count > 0{
                let contentController = getContentViewController(withIndex: 0)!
                let contentControllers = [contentController]

                pageController.setViewControllers(contentControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)

            }

            pageViewController = pageController

            self.addChildViewController(pageViewController!)

            self.view.addSubview(pageViewController!.view)
            pageViewController!.didMove(toParentViewController: self)

        }

I tried to change it for this:

let contentController = getContentViewController(withIndex: 0)!
let contentController1 = getContentViewController(withIndex: 1)!
let contentControllers = [contentController, contentController1]

But in this case my images not showing in pages. It is not help. What am I doing wrong? How to create UIPageViewController with spine mid location?

Update

import UIKit

class PageViewController: UIViewController {

    @IBOutlet weak var PageControl: UIPageControl!

    var pageViewController: UIPageViewController?
    var images = ["book1page1.png","book1","book1","book1page2.png","book1page1.png","book1page2.png"]
    var pendingIndex: Int?

    override func viewDidLoad() {
        super.viewDidLoad()

        createPageViewController()
        setupPageControll()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func createPageViewController() {
        // Instantiate the PageViewController
        let pageController = self.storyboard?.instantiateViewController(withIdentifier: "PageViewController") as! UIPageViewController
        pageController.dataSource = self
        pageController.delegate = self

        if images.count > 0{
            let firstController = getContentViewController(withIndex: 0)!
            let contentControllers = [firstController]

            pageController.setViewControllers(contentControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)

        }

        pageViewController = pageController

        self.addChildViewController(pageViewController!)

        //self.view.addSubview(pageViewController!.view)
        self.view.insertSubview(pageViewController!.view, at: 0)
        pageViewController!.didMove(toParentViewController: self)

    }

    //Setup Pagination Icons and count
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return images.count
    }

    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        return 0
    }

    func setupPageControll(){
        let apperance = UIPageControl.appearance()
        apperance.pageIndicatorTintColor = UIColor.gray
        apperance.currentPageIndicatorTintColor = UIColor.white
        apperance.backgroundColor = UIColor.clear
    }

    func currentControllerIndex() -> Int{
        let pageItemController = self.currentConroller()

        if let controller = pageItemController as? ContentViewController {
            return controller.itemIndex
        }
        return -1
    }

    ///////////////////////////////////////////////

    func currentConroller() -> UIViewController?{
        if (self.pageViewController?.viewControllers?.count)! > 0{
            return self.pageViewController?.viewControllers![0]
        }

        return nil
    }

    func getContentViewController(withIndex index: Int) -> ContentViewController? {
        if index < images.count{
            let contentVC = self.storyboard?.instantiateViewController(withIdentifier: "ContentViewController") as! ContentViewController
            contentVC.itemIndex = index
            contentVC.imageName = images[index]

            return contentVC
        }

        return nil
    }

}

extension PageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        pendingIndex = (pendingViewControllers.first as! ContentViewController).itemIndex
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            let currentIndex = pendingIndex
            if let index = currentIndex {
                self.PageControl.currentPage = index
            }

        }
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let contentVC = viewController as! ContentViewController
        if contentVC.itemIndex > 0 {
            return getContentViewController(withIndex: contentVC.itemIndex - 1)
        }

        return nil
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let contentVC = viewController as! ContentViewController
        if contentVC.itemIndex + 1 < images.count {
            return getContentViewController(withIndex: contentVC.itemIndex + 1)
        }

        return nil
    }
}


Get this bounty!!!

#StackBounty: #ios #avfoundation #aac #avaudiopcmbuffer #avaudioconverter Decode AAC to PCM format using AVAudioConverter Swift

Bounty: 300

How convert AAC to PCM using AVAudioConverter, AVAudioCompressedBuffer and AVAudioPCMBuffer on Swift?

On WWDC 2015, 507 Session was said, that AVAudioConverter can encode and decode PCM buffer, was showed encode example, but wasn’t showed examples with decoding.
I tried decode, and something doesn’t work. I don’t know what:(

Calls:

//buffer - it's AVAudioPCMBuffer from AVAudioInputNode(AVAudioEngine)
let aacBuffer = AudioBufferConverter.convertToAAC(from: buffer, error: nil) //has data
let data = Data(bytes: aacBuffer!.data, count: Int(aacBuffer!.byteLength)) //has data
let aacReverseBuffer = AudioBufferConverter.convertToAAC(from: data) //has data
let pcmReverseBuffer = AudioBufferConverter.convertToPCM(from: aacBuffer2!, error: nil) //zeros data. data object exist, but filled by zeros

It’s code for converting:

class AudioBufferFormatHelper {

    static func PCMFormat() -> AVAudioFormat? {

        return AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 1, interleaved: false)
    }

    static func AACFormat() -> AVAudioFormat? {

        var outDesc = AudioStreamBasicDescription(
                mSampleRate: 44100,
                mFormatID: kAudioFormatMPEG4AAC,
                mFormatFlags: 0,
                mBytesPerPacket: 0,
                mFramesPerPacket: 0,
                mBytesPerFrame: 0,
                mChannelsPerFrame: 1,
                mBitsPerChannel: 0,
                mReserved: 0)
        let outFormat = AVAudioFormat(streamDescription: &outDesc)
        return outFormat
    }
}

class AudioBufferConverter {

    static func convertToAAC(from buffer: AVAudioBuffer, error outError: NSErrorPointer) -> AVAudioCompressedBuffer? {

        let outputFormat = AudioBufferFormatHelper.AACFormat()
        let outBuffer = AVAudioCompressedBuffer(format: outputFormat!, packetCapacity: 8, maximumPacketSize: 768)

        self.convert(from: buffer, to: outBuffer, error: outError)

        return outBuffer
    }

    static func convertToPCM(from buffer: AVAudioBuffer, error outError: NSErrorPointer) -> AVAudioPCMBuffer? {

        let outputFormat = AudioBufferFormatHelper.PCMFormat()
        guard let outBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat!, frameCapacity: 4410) else {
            return nil
        }

        outBuffer.frameLength = 4410
        self.convert(from: buffer, to: outBuffer, error: outError)

        return outBuffer
    }

    static func convertToAAC(from data: Data) -> AVAudioCompressedBuffer? {

        let nsData = NSData(data: data)
        let inputFormat = AudioBufferFormatHelper.AACFormat()
        let buffer = AVAudioCompressedBuffer(format: inputFormat!, packetCapacity: 8, maximumPacketSize: 768)
        buffer.byteLength = UInt32(data.count)
        buffer.packetCount = 8

        buffer.data.copyMemory(from: nsData.bytes, byteCount: nsData.length)
        buffer.packetDescriptions!.pointee.mDataByteSize = 4

        return buffer
    }

    private static func convert(from sourceBuffer: AVAudioBuffer, to destinationBuffer: AVAudioBuffer, error outError: NSErrorPointer) {

        //init converter
        let inputFormat = sourceBuffer.format
        let outputFormat = destinationBuffer.format
        let converter = AVAudioConverter(from: inputFormat, to: outputFormat)

        converter!.bitRate = 32000

        let inputBlock : AVAudioConverterInputBlock = { inNumPackets, outStatus in

            outStatus.pointee = AVAudioConverterInputStatus.haveData
            return sourceBuffer
        }

        _ = converter!.convert(to: destinationBuffer, error: outError, withInputFrom: inputBlock)
    }
}

In result AVAudioPCMBuffer has data with zeros.
And in messages I see errors:

AACDecoder.cpp:192:Deserialize:  Unmatched number of channel elements in payload
AACDecoder.cpp:220:DecodeFrame:  Error deserializing packet
[ac] ACMP4AACBaseDecoder.cpp:1337:ProduceOutputBufferList: (0x14f81b840) Error decoding packet 1: err = -1, packet length: 0
AACDecoder.cpp:192:Deserialize:  Unmatched number of channel elements in payload
AACDecoder.cpp:220:DecodeFrame:  Error deserializing packet
[ac] ACMP4AACBaseDecoder.cpp:1337:ProduceOutputBufferList: (0x14f81b840) Error decoding packet 3: err = -1, packet length: 0
AACDecoder.cpp:192:Deserialize:  Unmatched number of channel elements in payload
AACDecoder.cpp:220:DecodeFrame:  Error deserializing packet
[ac] ACMP4AACBaseDecoder.cpp:1337:ProduceOutputBufferList: (0x14f81b840) Error decoding packet 5: err = -1, packet length: 0
AACDecoder.cpp:192:Deserialize:  Unmatched number of channel elements in payload
AACDecoder.cpp:220:DecodeFrame:  Error deserializing packet
[ac] ACMP4AACBaseDecoder.cpp:1337:ProduceOutputBufferList: (0x14f81b840) Error decoding packet 7: err = -1, packet length: 0


Get this bounty!!!

#StackBounty: #ios #angularjs #cordova #cordova-plugins #ionic-v1 Run socket Server in a ionic1 application

Bounty: 50

Here is the requirement:

Start a socket server in the app which will receive messages from another app running in the same device.

Here is the stack:

Using the chrome’s tcpServer plugin, I ended this code:

angular.module('starter', ['ionic'])
.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        var tcpServer = window.chrome && window.chrome.sockets &&
            window.chrome.sockets.tcpServer;

        if(tcpServer) {  
            console.log('LOG tcpServer present')
            tcpServer.create({}, function (createInfo) {
                var serverSocketId = createInfo.socketId;
                console.log('LOG', 'serverSocketId', serverSocketId)
                if (serverSocketId > 0) {
                    tcpServer.listen(serverSocketId, '0.0.0.0', 8080, 50, function(resultCode) {
                        console.log('LOG', 'listening', resultCode)
                    });
                } else {
                  console.log('LOG', 'Unable to create socket');
                }

            });
        } else {
            console.log('LOG', 'missing chrome.sockets.tcpServer')
        }
    });
})

And the result of this code is:

LOG tcpServer present
LOG serverSocketId 0
LOG Unable to create socket

Given that I’ve no experience in ionic1/iOS:

  • Is this my option to create this socket server?
  • 0 means that the create method failed. What could I be doing wrong here?
  • Do I need some special permission in iOS to perform those actions? (Similar to Android)
  • I couldn’t find resources/examples to help me doing this. Where should I search?

Any help is appreciated


Get this bounty!!!