#StackBounty: #swift #generics #core-data #protocols #swift-protocols Populating objects from cloud records (or another external source…

Bounty: 150

I’m building a generic API for my Swift applications. I use CoreData for local storage and CloudKit for cloud synchronization.

in order to be able to work with my data objects in generic functions I have organized them as follows (brief summary):

  • Objects that go in the CoreData Database are NSManagedObject instances that conform to a protocol called ManagedObjectProtocol, which enables conversion to DataObject instances
  • NSManagedObjects that need to be cloud synced conform to a protocol called CloudObject which allows populating objects from records and vice-versa
  • Objects I use in the graphic layer of my apps are NSObject classes that conform to the DataObject protocol which allows for conversion to NSManagedObject instances

an object of a specific class. What I would like this code to look like is this:

for record in records {
    let context = self.persistentContainer.newBackgroundContext()
    //classForEntityName is a function in a custom extension that returns an NSManagedObject for the entityName provided. 
    //I assume here that recordType == entityName
    if let managed = self.persistentContainer.classForEntityName(record!.recordType) {
        if let cloud = managed as? CloudObject {   
            cloud.populateManagedObject(from: record!, in: context)
        }
    }
 }

However, this gives me several errors:

Protocol 'CloudObject' can only be used as a generic constraint because it has Self or associated type requirements
Member 'populateManagedObject' cannot be used on value of protocol type 'CloudObject'; use a generic constraint instead

The CloudObject protocol looks as follows:

protocol CloudObject {
    associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol

    var recordID: CKRecordID? { get }
    var recordType: String { get }

    func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudManagedObject>
    func populateCKRecord() -> CKRecord
}

Somehow I need to find a way that allows me to get the specific class conforming to CloudObject based on the recordType I receive. How would I Best go about this?

Any help would be much appreciated!


Get this bounty!!!

#StackBounty: #swift How do you make a method appear in the "overload set" only if another method can be called?

Bounty: 100

Consider an extension on NotificationCenter which does special things for notification objects that conform to certain protocols (yes these could be more common but it’s an example to help demonstrate my real question).

extension NotificationCenter {

  func addObserver<Note: BasicNotification>(using block: @escaping (Note) -> ())
  -> NotificationToken {
    let observer = addObserver(forName: Note.notificationName, object: nil,
        queue: nil, using: { note in
      block(Note(notification: note))
    })
    return NotificationToken(observer: observer, center: self)
  }

  func addObserver<Note: CustomNotification>(using block: @escaping (Note) -> ())
  -> NotificationToken {
    let observer = addObserver(forName: Note.notificationName, object: nil,
        queue: nil, using: { note in
      block(note.object as! Note)
    })
    return NotificationToken(observer: observer, center: self)
  }

}

Now, consider when we want this notification to only fire once, and then deregister…

extension NotificationCenter {

  func observeOnce<Note: BasicNotification>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

  func observeOnce<Note: CustomNotification>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

}

This is the exact same code. What I really want is one observeOnce method – I don’t want to have to write two of them.

If I don’t use any conditional conformance…

  func observeOnce<Note>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

I get an error Cannot invoke 'addObserver' with an argument list of type '(using: (Note) -> ())' which makes perfect sense.

If I use a common base protocol (which both conform to)…

  func observeOnce<Note: SomeNotification>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

I get the exact same error, which does not quite make as much sense – I would expect something about the call being ambiguous rather than not existing at all.

Having seen the & operator mean conformance to multiple protocols, I did try using BasicNotification | CommonNotification in the extremely unlikely case where that may have some meaning… but of course it did not work.

I’v also tried a bunch of other alternatives, to no avail. What I am trying to do is have observeOnce be available to call if either of the others are available to call.

In C++, I would do something like this (didn’t run it through a compiler – hopefully you get what I’m trying to do)…

template <typename T>
auto observeOnce(std::function<void (T)> block)
-> decltype(void(this->addObserver(std::move(block))))
{
  // do my stuff here
}

The above code basically means that the function observeOnce only appears in the overload set if addObserver(std::move(block)) can be called.

So, what is the swift way of accomplishing the same thing?


Get this bounty!!!

#StackBounty: #ios #swift #nsattributedstring #core-text Why does this call to NSMutableAttributedString addAttributes crash?

Bounty: 100

I’m getting a frequent crash on calling addAttributes out in the field that I can’t reproduce (EXC_BREAKPOINT). Have added checks on the NSRange I’m passing in and surprisingly it still crashes… any ideas very gratefully received!

let boldAttributes : [NSAttributedStringKey: Any] = [.font: cellStyle.boldFont()]
if (nsrange.location >= 0 && nsrange.length > 0 && nsrange.location + nsrange.length <= myAttributedString.length) {
    myAttributedString.addAttributes(boldAttributes, range: nsrange)
}

Crash log added by request:

#0. Crashed: com.apple.main-thread
0  MyApp                   0x10054ae38 specialized UIAccessorizedTextField.accessoryAttributedString(IndexPath, cellStyle : UIAccessorizedTextFieldCellStyle) -> NSAttributedString (UIAccessorizedTextField.swift:322)
1  MyApp                   0x10054b104 specialized UIAccessorizedTextField.collectionView(UICollectionView, cellForItemAt : IndexPath) -> UICollectionViewCell (UIAccessorizedTextField.swift:345)
2  MyApp                   0x10054860c @objc UIAccessorizedTextField.collectionView(UICollectionView, cellForItemAt : IndexPath) -> UICollectionViewCell (UIAccessorizedTextField.swift)
3  UIKit                          0x18b229f3c -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:] + 356
4  UIKit                          0x18bb90b68 -[UICollectionView _prefetchItemsForVelocity:maxItemsToPrefetch:invalidateCandidatesOnDirectionChanges:] + 508
5  UIKit                          0x18b2088b4 -[UICollectionView layoutSubviews] + 760
6  UIKit                          0x18b0d76f4 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1420
7  QuartzCore                     0x18564dfec -[CALayer layoutSublayers] + 184
8  QuartzCore                     0x18565217c CA::Layer::layout_if_needed(CA::Transaction*) + 324
9  QuartzCore                     0x1855be830 CA::Context::commit_transaction(CA::Transaction*) + 320
10 QuartzCore                     0x1855e6364 CA::Transaction::commit() + 580
11 QuartzCore                     0x1855e71e4 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 92
12 CoreFoundation                 0x18146e910 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
13 CoreFoundation                 0x18146c238 __CFRunLoopDoObservers + 412
14 CoreFoundation                 0x18146c884 __CFRunLoopRun + 1436
15 CoreFoundation                 0x18138cda8 CFRunLoopRunSpecific + 552
16 GraphicsServices               0x183371020 GSEventRunModal + 100
17 UIKit                          0x18b3a9758 UIApplicationMain + 236
18 MyApp                   0x1003f2830 main (main.m:16)
19 libdyld.dylib                  0x180e1dfc0 start + 4


Get this bounty!!!

#StackBounty: #ios #swift #xcode #avfoundation #avplayer AVPlayerLayer in UICollectionViewCell, or how to load gifs as WhatsApp

Bounty: 50

In the app I have a UICollectionView with item size so that about ~20 items are visible on the screen at the same time. The content that I want to display in each cell is Gif image downloaded from Giphy / Tenor.

However, I realized that gif files take much more space (and time to load) than the relative mp4 files that both Tenor and Giphy provide for each animated image, which is actually obvious, cause mp4 file format has a compressing logic and stuff like that. Sorry if I use wrong terms.

In order to have list loaded faster I decided to switch using UIImageView with GIF images to AVPlayerLayer, cause mp4 file is like ~10x lighter than GIF image. But I faced with the performance issue similar to what described HERE. The flow is mostly the same, I have 20+ items visible at the same time, but because of hardware limitations it only shows 16 videos. I couldn’t find any workaround or any other frameworks that would allow to have more than 16 AVPlayerLayer showing video at the same time.

I’m wondering how WhatsApp application works and handles this logic. It also has GIF selection from Tenor. I already checked and figured out that WhatsApp downloads small video files and not gif images. That’s why it loads very fast. But I have no idea how they can show 20+ items at the same time. HERE is how that works in WhatsApp – https://media.giphy.com/media/33E84h3RAVn0vQWZak/giphy.gif. Also, I notices during scroll the small static previews are showing, but I don’t see the app making requests for it. Probably they gets a first frame of the gif on the fly without any delays in main thread.

I also tried that, but even if I make every single stuff in background thread and the only line on the main thread is “self.imageView.image = myImage”, it anyway is lugging a little bit if I have 8 items in the row for example and scrolling very fast.

I see only 2 possible solutions to have it loads fast (so we definitely need to load mp4 instead of gifs), and scroll smooth and without lugs:
1. WhatsApp uses its own custom Video Core to display video in the UICollectionViewCell .
2. WhatsApp downloads video to speed up the download process but then encodes mp4 file to gif one on the fly and use regular animated UIImageView to show the output gif file. However I was not able to have this flow working very fast without lugging during ‘massive’ scrolling

Any thoughts on how to implement the same to make it works fast and smooth as in WhatsApp? I’m unable to check how it handles the downloaded info, but for sure it downloads mp4 files and not gif ones.


Get this bounty!!!

#StackBounty: #swift #ios #user-interface Page and center UICollectionView like App Store

Bounty: 50

I need a collection view to page through cells and center it like the App Store, where a portion of the previous and next cells look like this:

enter image description here

The native isPagingEnabled flag would be great if it had an option to center on the cell. However, making the cell span the full width of the collection view doesn’t show previous/next cells, and making the cell width smaller doesn’t snap to center when paging.

There are tons of hacks, articles, and suggestions out there that I’ve tried. Many were overcomplicated or a horrible UX. I finally find a good balance with this example and adjusted the code.

This is the code and scroll delegate event to make this work:

class HomeViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    private var indexOfCellBeforeDragging = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        let layout = UICollectionViewFlowLayout()
        let width = view.frame.width
        let height: CGFloat = 275
        let inset: CGFloat = 30
        layout.itemSize = CGSize(width: width - inset * 2, height: height)
        layout.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
        layout.minimumLineSpacing = 0
        layout.scrollDirection = .horizontal
        collectionView.collectionViewLayout = layout
        collectionView.frame = CGRect(x: 0, y: 0, width: width, height: height)
    }

    ...
}

extension HomeViewController: UICollectionViewDelegate {

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        indexOfCellBeforeDragging = indexOfMajorCell(for: collectionView)
    }

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }

        // Stop scrollView sliding
        targetContentOffset.pointee = scrollView.contentOffset

        // Calculate where scrollView should snap to
        let indexOfMajorCell = self.indexOfMajorCell(for: collectionView)

        // Calculate conditions
        let dataSourceCount = collectionView(collectionView!, numberOfItemsInSection: 0)
        let swipeVelocityThreshold: CGFloat = 0.5 // After some trail and error
        let hasEnoughVelocityToSlideToTheNextCell = indexOfCellBeforeDragging + 1 < dataSourceCount && velocity.x > swipeVelocityThreshold
        let hasEnoughVelocityToSlideToThePreviousCell = indexOfCellBeforeDragging - 1 >= 0 && velocity.x < -swipeVelocityThreshold
        let majorCellIsTheCellBeforeDragging = indexOfMajorCell == indexOfCellBeforeDragging
        let didUseSwipeToSkipCell = majorCellIsTheCellBeforeDragging
            && (hasEnoughVelocityToSlideToTheNextCell || hasEnoughVelocityToSlideToThePreviousCell)

        guard didUseSwipeToSkipCell else {
            // Better way to scroll to a cell
            return collectionView.scrollToItem(
                at: IndexPath(row: indexOfMajorCell, section: 0),
                at: .centeredHorizontally,
                animated: true
            )
        }

        let snapToIndex = indexOfCellBeforeDragging + (hasEnoughVelocityToSlideToTheNextCell ? 1 : -1)
        let toValue = layout.itemSize.width * CGFloat(snapToIndex)

        // Damping equal 1 => no oscillations => decay animation
        UIView.animate(
            withDuration: 0.3,
            delay: 0,
            usingSpringWithDamping: 1,
            initialSpringVelocity: velocity.x,
            options: .allowUserInteraction,
            animations: {
                scrollView.contentOffset = CGPoint(x: toValue, y: 0)
                scrollView.layoutIfNeeded()
            },
            completion: nil
        )
    }

    private func indexOfMajorCell(for collectionView: UICollectionView) -> Int {
        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
        let itemWidth = layout.itemSize.width
        let proportionalOffset = collectionView.contentOffset.x / itemWidth
        return Int(round(proportionalOffset))
    }
}

Do you have any suggestions or advice on how to simplify this or make it reusable? I don’t like how everything is not encapsulated in the extension (i.e., the private property indexOfCellBeforeDragging is outside). Also, any issues that come to mind?

UPDATE:

I found a better way to encapsulate by subclassing a UICollectionViewFlowLayout:

class HomeViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let width = view.frame.width
        let height: CGFloat = 275
        collectionView.collectionViewLayout = SnapPagingLayout(width: width, height: height, inset: 30)
        collectionView.frame = CGRect(x: 0, y: 0, width: width, height: height + 40)
    }

    ...
}

extension HomeViewController: UICollectionViewDelegate {

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else { return }
        layout.willBeginDragging()
    }

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else { return }
        layout.willEndDragging(withVelocity: velocity, targetContentOffset: targetContentOffset)
    }

}

class SnapPagingLayout: UICollectionViewFlowLayout {
    private var indexOfCellBeforeDragging = 0

    convenience init(width: CGFloat, height: CGFloat, inset: CGFloat) {
        self.init()

        self.itemSize = CGSize(width: width - inset * 2, height: height)
        self.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
        self.minimumLineSpacing = 0
        self.scrollDirection = .horizontal
    }
}

private extension SnapPagingLayout {

    func indexOfMajorCell() -> Int {
        guard let collectionView = collectionView else { return 0 }
        let proportionalOffset = collectionView.contentOffset.x / itemSize.width
        return Int(round(proportionalOffset))
    }
}

extension SnapPagingLayout {

    func willBeginDragging() {
        indexOfCellBeforeDragging = indexOfMajorCell()
    }

    func willEndDragging(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        guard let collectionView = collectionView else { return }

        // Stop scrollView sliding
        targetContentOffset.pointee = collectionView.contentOffset

        // Calculate where scrollView should snap to
        let indexOfMajorCell = self.indexOfMajorCell()

        guard let dataSourceCount = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: 0),
            dataSourceCount > 0 else {
                return
        }

        // Calculate conditions
        let swipeVelocityThreshold: CGFloat = 0.5 // After some trail and error
        let hasEnoughVelocityToSlideToTheNextCell = indexOfCellBeforeDragging + 1 < dataSourceCount && velocity.x > swipeVelocityThreshold
        let hasEnoughVelocityToSlideToThePreviousCell = indexOfCellBeforeDragging - 1 >= 0 && velocity.x < -swipeVelocityThreshold
        let majorCellIsTheCellBeforeDragging = indexOfMajorCell == indexOfCellBeforeDragging
        let didUseSwipeToSkipCell = majorCellIsTheCellBeforeDragging
            && (hasEnoughVelocityToSlideToTheNextCell || hasEnoughVelocityToSlideToThePreviousCell)

        guard didUseSwipeToSkipCell else {
            // Better way to scroll to a cell
            return collectionView.scrollToItem(
                at: IndexPath(row: indexOfMajorCell, section: 0),
                at: .centeredHorizontally,
                animated: true
            )
        }

        let snapToIndex = indexOfCellBeforeDragging + (hasEnoughVelocityToSlideToTheNextCell ? 1 : -1)
        let toValue = itemSize.width * CGFloat(snapToIndex)

        // Damping equal 1 => no oscillations => decay animation
        UIView.animate(
            withDuration: 0.3,
            delay: 0,
            usingSpringWithDamping: 1,
            initialSpringVelocity: velocity.x,
            options: .allowUserInteraction,
            animations: {
                collectionView.contentOffset = CGPoint(x: toValue, y: 0)
                collectionView.layoutIfNeeded()
            },
            completion: nil
        )
    }
}

Do you have any advice or suggestions on optimizing or a better UX? It doesn’t feel as smooth as the App Store (especially when scrolling fast), but it’s almost there. Also, it misbehaves in landscape or in iPad; I think there needs to be some kind of cancellation if something like more than 1 cell can fit on the screen!


Get this bounty!!!

#StackBounty: #ios #swift #vpn #networkextension #l2tp Implementing VPN with L2TP protocol in iOS app

Bounty: 100

In iOS settings there are options to create VPN configuration using IPSec, IKEv2 and L2TP. Using NetworkExtension framework from Apple there’s option to create VPN using IPSec and IKEv2 protocols only. They do work but problem is that I need to create connection via L2TP since that’s only supported by company’s firewall.

There’s question iOS app with custom VPN connect from 2014 and it’s answered with:

If you want to connect programatically in ios 8 you can use only IPSec or IKEv2 protocols. L2TP and PPTP protocols are private for apple. It is not possible to use L2TP and PPTP APIs in your applications. Only Apple is currently using these APIs.

Is there any way to create L2TP VPN connection from iOS application (Swift)?


Get this bounty!!!

#StackBounty: #ios #swift #xcode #ios-charts Returning to xcode/swift/ios project – suddenly get "no such module"

Bounty: 100

I think xcode is having a laugh at me.

I open my old project that uses charts library
https://github.com/danielgindi/Charts/

and get error

No such module Charts

where I use

import Charts

I then tried to remove Charts from my project. Downloaded new project. And dragged Charts.xcodeproj file into my project – no change

The way my files physically are organized is:

 xcode-ios-projects
 - charts-ios
 - yourappplaform
 -- appcodegeneric (my source code and different .plist files for different apps)
 -- different-apps-asset-folder-1
 -- different-apps-asset-folder-2
 -- different-apps-asset-folder-x

Inside xcode IDE structure looks like this

appgeneric
- Charts.xcodeproj
- appcodegeneric
-- different-apps-asset-folder-1
-- different-apps-asset-folder-2
-- different-apps-asset-folder-x

I have updated xcode, charts etc. to newest. I have not yet updated to Swift 4 conversion process

It has been a long time since I touched my ios/swift project, so maybe I am missing something obvious – but this problem seems a bit odd to me.

UPDATE
After using “Clean” I now get the following error when I “Build”:

Module compiled with Swift 3.0.2 cannot be imported in Swift 3.3:
/Users/myname/Library/Developer/Xcode/kukuhkhkuhkuh/Build/Products/Debug-iphonesimulator/Charts.framework/Modules/Charts.swiftmodule/x86_64.swiftmodule

However – after cleaning that folder manually in Finder… I know get the same error as in the beginning:

no such module ‘Charts’


Get this bounty!!!

#StackBounty: #ios #swift #camera #core-graphics #avcapturemoviefileoutput How to compose AVCaptureVideoPreviewLayer with other views o…

Bounty: 500

I have managed to setup a basic AVCaptureSession which records a video and saves it on device by using AVCaptureFileOutputRecordingDelegate. I have been searching through docs to understand how we can add statistics overlays on top of the video which is being recorded.

i.e.

enter image description here

As you can see in the above image. I have multiple overlays on top of video preview layer. Now, when I save my video output I would like to compose those views onto the video as well.

What have I tried so far?

  • Honestly, I am just jumping around on internet to find a reputable blog explaining how one would do this. But failed to find one.
  • I have read few places that one could render text layer overlays as described in following post by creating CALayer and adding it as a sublayer.
  • But, what about if I want to render MapView on top of the video being recorded. Also, I am not looking for screen capture. Some of the content on the screen will not be part of the final recording so I want to be able to cherry pick view that will be composed.

What am I looking for?

  1. Direction.
  2. No straight up solution
  3. Documentation link and class names I should be reading more about to create this.

Progress So Far:

I have managed to understand that I need to get hold of CVImageBuffer from CMSampleBuffer and draw text over it. There are things still unclear to me whether it is possible to somehow overlay MapView over the video that is being recorded.


Get this bounty!!!

#StackBounty: #ios #swift #uitableview #uiscrollview Can UITableView scroll with UICollectionView inside it?

Bounty: 50

I have the structures below…

enter image description here

I wrap two of collection views into tableview

One is in tableview header(Collection1), another is in tableview 1st row(Collection2).

All the functions are good (Both collection view).

just…

When I scroll up in Collection2, Collection1 will Not scroll up together, because I’m only scrolling the collectionViews not the tableview.

It only scroll together when I scroll in Collection1.

Is it possible to make the header view scroll with user just like app store’s index carousel header?

Or I just went to the wrong place, I should use other ways to approach.


Get this bounty!!!

#StackBounty: #swift #xcode #animation #uiviewanimation #framerjs Determine springWithDamping and initialSpringVelocity based off from …

Bounty: 100

My design team gives us animation parameters using friction and tension. For instance:

Has a spring affect (280 tension and 20.5 friction) Over 0.3 seconds

Unfortunately, i’ve always guessed what these values convert to, and eyeball it, if it looks close I send it over and they approve it. But the time that it takes to constantly build the project with different values is time consuming. There has to be an easier way.

I found Framer on Github and it’s led me to believe that the damping can be calculated like so:

let damping: CGFloat = 20.5 / (2 * (1 * 280)).squareRoot()

However, I cannot seem to figure out how to calculate the velocity based off from friction and tension. Is there an easier way to save this developer some valuable time?

Example of animation:

UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: damping,
                       initialSpringVelocity: ???, options: .curveEaseIn, animations: { // Do Stuff })


Get this bounty!!!